Changelog

Live Billing And Promotion Codes

Patina 0.6 turns the live Stripe billing on. The Pro and Basic plan tiers introduced in 0.5.35 are now backed by real production payments, with redeemable promotion codes, a cleaner Settings page, and a small Quick Journal polish.

Live billing is on

  • The Stripe live keys, recurring Price IDs, and webhook signing secret introduced in 0.5.35 are now wired through to production. Patina Basic and Patina Pro plans both create real Stripe Checkout sessions and respect the Pro entitlement when the webhook returns.
  • Existing Lifetime License holders see no functional change.
  • Existing annual subscribers stay on Patina Basic with no action needed.

Promotion codes in checkout

  • SubscriptionService.checkout(plan:email:promotionCode:) now forwards an optional promotion code through to the Cloudflare worker. The worker validates it against Stripe, applies the discount to the Checkout Session, and (for 100% off codes like the new EARLYBIRD coupon) skips Stripe Checkout entirely and flips the user to Pro server-side. The client picks that up via the new redeemed field on the checkout response and refreshes the subscription status without sending the user through a hosted page.
  • Two new operator scripts ship in scripts/:
  • create-earlybird-code.mjs provisions or refreshes the EARLYBIRD promotion code in live Stripe with the right product scoping and expiration.
  • check-earlybird-code.mjs looks the code up by name and prints status, redemption count, max redemptions, and coupon shape.
  • Both scripts read the live secret from a hidden TTY prompt so the key never touches shell history.

Settings polish

  • The fair-use bars in Settings now show weekly requests plus the weekly AI budget used by the Patina plan. The worker enforces the same weekly cost ceiling, so the UI matches the cap users actually hit.
  • The macOS-only "Background Sync" section has been removed from General Settings. Background sync still runs on its own cadence, but the manual "Refresh Now" control was confusing to people who hadn't connected a source yet.
  • The onboarding-screen debug picker that used to appear under Design Previews is now gated behind com.gabrielvaldivia.patina.debug, so production builds no longer surface it even when other design preview affordances are on.

Quick Journal

  • The Save button in the Quick Journal overlay now uses solid white foreground text whenever it is enabled. The previous Color.dynamic call flipped to black on dark backgrounds, which washed out against the filled accent pill in dark mode.

Patina Pro

Patina 0.5.35 introduces the Patina Pro plan tier, with the Stripe live wiring and onboarding/settings UI to back it.

  • A new Patina Pro plan now sits alongside the existing tier (renamed to Patina Basic) and the Lifetime License. Pro is positioned for heavier daily usage, with a higher fair-use ceiling than Basic.
  • PlanPickerCard, the onboarding welcome step, the settings plan management view, and the in-app pricing flows all carry the new three-tier setup. Picking a plan in onboarding and switching plans in settings both route through the same model.
  • The Patina Basic feature line now says "Roughly 125 AI requests per week under our fair-use policy" instead of the vague catch-all wording, so the weekly ceiling is clear before you sign up.

Names

  • The Gemini-backed AI provider is now labeled "Patina AI" in the settings UI, since "Patina Subscription" used to collide with the plan tier name and made the Settings → AI Provider picker confusing. Functionally identical to before, just a rename.

Stripe Live Mode

  • New scripts/setup-stripe-live.mjs walks an operator through configuring the live Stripe key, the Pro recurring Price ID, and the webhook secret. Backend's wrangler config documents the new STRIPE_PRICE_PRO secret next to the existing ones.
  • SubscriptionService and the Cloudflare worker pick up handlers for the new Pro price id with matching test coverage in SubscriptionStatusTests.

Infrastructure

  • Root wrangler.jsonc lets the landing site deploy through wrangler deploy directly, without needing the Backend folder as the working dir. The Backend worker still uses its own Backend/wrangler.toml.
  • .gitignore now blocks .dev.vars* and .env* so the Stripe live setup script can't accidentally leak new secrets into git. Committed .example files are still allowed for documentation.

Tests

  • 503/503 macOS tests pass with the Pro tier and naming changes in place. Backend worker tests all green.

Sharper Home Dashboard

Patina 0.5.34 reshuffles the Home dashboard's default widget layout, sharpens the Today blurb's emphasis rendering, and limits meeting-prep treatment to the meeting that's actually next.

Home Dashboard

  • Default widget order has been reworked. Day and Weather now sit as compact 1-column widgets at the top, with Today expanding into the 2-column slot beside them. People moved up the stack and now appears immediately after the timeline widgets, with To Do directly below it.
  • The Today widget's bolded emphasis spans are filtered through a stopword list so common connective words like "a", "an", "the", "but", "for", and "in" no longer get treated as significant tokens. Only proper nouns and meaningful phrases stay emphasized.

Meeting Prep

  • The dashboard's next-event list now passes its visible events through the same prep-display filter the Up Next surface uses. The "prep this" treatment lands only on the upcoming meeting that's actually next, instead of every visible future event.

Up Next

  • UpNextViewModel.prepDisplayEvents is now a shared static helper so the Home dashboard and the Up Next view agree on which events get the prep affordance. New tests pin the contract: only the very next eligible event qualifies, and events already passed get filtered out before prep evaluation.

Onboarding And Site

  • Onboarding's intro step picks up a small copy refinement.
  • The bundled README and the landing site (patina.md) got a substantive refresh: new hero, new pricing layout, smoother waitlist flow, and updated marketing photos. Visible to people visiting the site, not the app itself.

Tests

  • 502/502 macOS tests pass. New coverage for prepDisplayEvents semantics in UpNextActionCapabilitiesTests.

PATINA.md And Leaner Sync

Patina 0.5.33 renames the user-side project memory file from CLAUDE.md to PATINA.md, polishes another round on the Home dashboard, and cuts disk + cost overhead across the app.

PATINA.md

  • The user-facing project memory file is now PATINA.md. Patina reads it at the start of every chat to understand who you are, what matters to you, and how you want to be coached. New installs land on PATINA.md directly; existing folders with a legacy CLAUDE.md keep working because the app falls back to it when PATINA.md is absent.
  • Onboarding writes PATINA.md for new setups. The system prompt, base prompt, and onboarding flow all carry the new name end to end.
  • The user instructions block inside the system prompt is now labeled by source file, so PATINA.md and legacy CLAUDE.md users see prompts annotated with where their setup came from.

Home Dashboard

  • Widget edit sheet, widget chrome, and the prompt dock pick up another iteration of polish: cleaner edit affordances, sharper layout, calmer focus states.
  • HomeModels and the dashboard view model get small fixes that line up widget sizing, content rows, and shell behavior with the rest of the navigation.

Up Next

  • Card capabilities and action plumbing pick up a substantial pass with matching test coverage. Cards behave more consistently when an action references a connected source vs. local-only context.
  • Capability filtering gets stricter so unsupported actions stop slipping into card menus.

Chat And Onboarding Polish

  • Markdown rendering, tool-call chips, and user-profile bits get a small set of fixes for layout and selection behavior.
  • Onboarding skill templates, automation seeds, and people seeding pick up matching tweaks so new installs land closer to the live shape.

Disk And Cost

  • Person-photo cache is capped at 512px / JPEG 0.85. Upstream avatars (Google Contacts, Gravatar, Slack) often arrive at 256–1024px; the cache used to balloon to 12 MB+ for heavy users. Now it stays in the hundreds-of-KB range.
  • A daily orphan-prune sweep removes Gemini session + agent records that aren't referenced by any chat thread. Background automations (morning brief, digest, sync) used to leave hundreds of orphan records per day; the sweep keeps .patina/ bounded.
  • The base system prompt now tells the agent to redirect shell tool outputs to /tmp/ instead of the user's Patina folder, so reflexive cmd > out.txt writes from the agent stop cluttering the iCloud-synced data root.
  • Gemini conversation history is now folded into the shared cachedContents resource alongside the system prompt and tools, cutting the cost of long chats by reusing a single cache across many turns. Visible to Patina's bill, not to the user, but it pays for itself within a few exchanges.

Tests

  • 500/500 macOS tests pass. Coverage added for the avatar resize, the orphan sweep, the history-cache fold, and the system-prompt rename labeling.

Dashboard Returns

This release restores Dashboard as a first-class tab and polishes the top-level navigation controls so moving around Patina feels more predictable.

Dashboard

  • Restores Dashboard as its own destination, separate from Today.
  • Adds richer dashboard widget options for People, Files, Journal, clocks, and filtered to-do lists.
  • Lets dashboard widgets open their matching areas directly, including People, Files, Journal, and the canonical To Do file.
  • Improves dashboard calendar titles by hiding internal metadata such as source, URL, organization, and notes fields.
  • Moves optional People and Files dashboard scans off the main UI path so opening Dashboard stays responsive even with large or iCloud-backed folders.

To Do

  • Adds app-style To Do controls for filtering active tasks, adding tasks, editing tasks, deleting tasks, reordering tasks, and showing recently solved items.
  • Keeps task checkbox toggles wired back to the underlying markdown file.
  • Adds tests around To Do file task toggles and dashboard widget model behavior.

Header Controls

  • Gives macOS header action buttons a consistent rounded square hit target.
  • Adds a subtle light gray hover state across back and forward buttons, page action buttons, dashboard controls, file actions, sort menus, and add buttons.
  • Makes the full button container clickable instead of requiring a precise click on the icon.

Reliability

  • Keeps Dashboard, Today, Chat, Files, Journal, and People panes resident during tab switches to preserve view model state.
  • Builds on the recent dashboard tab restoration while avoiding the beach-ball behavior seen when Dashboard performed synchronous local scans.

Cleaner Chat Links

Patina 0.5.31 is a polish release. Chat link underlines move to hover-only on macOS so paragraph text reads cleanly, modal dialogs stop leaking hover state to background rows, and the people-file index gets cached so repeated wiki-link resolution during agent linking stops re-scanning the people/ directory.

Chat Link Underlines

  • Links in chat messages no longer carry a permanent underline. On macOS, the underline appears only when the pointer is over the link, using NSTextView tracking areas to update the attribute on hover and clear it on exit or when text gets re-rendered.
  • The previous always-underline behavior on the Ink theme is gone. The link color (theme accent) still indicates the link, and the hover affordance is now consistent across themes.
  • Streaming markdown picks up the same default so partial messages match finished ones.

Modal Interaction Suppression

  • A new patinaModalInteractionSuppressed environment value gets set whenever Command Search, Quick Journal, or New To-Do is on screen. ToDoTaskRow reads this value and freezes its hover state while a modal is up, so the row tint and the "more actions" button stop showing through underneath the dialog.
  • The suppression flips the row's hovered flag off immediately when a modal opens, so a row that was already hovered when the modal appeared also resets.

People Wiki-Link Cache

  • UpNextWikiLinkFormatter now caches its scan of the people/ directory and the per-file title parse, keyed by Patina root. Repeated wiki-link resolution during agent linking and UpNext refreshes no longer re-reads every people file on every call.
  • The cache invalidates when the Patina root changes and when patinaFilesChanged reports a change inside people/. Notifications from other folders leave the cache intact.
  • New test covers the cache: a change to a people file is only reflected after the matching invalidation event, while unrelated change paths keep the cached result.

Quick Journal

  • Quick Journal overlay grows from 90% to 96% of available height, so the editor uses the full window without the previous gap at the top.

Files Page Creation Tools

Patina 0.5.30 is a Files release. The Files page learns enough creation, navigation, and editing affordances that opening a folder and starting a new note no longer requires a chat round-trip. Digest titles also become consistent across the app, so older digests stop showing four different date formats.

The release also refreshes the bundled onboarding guides so they describe the app the way users actually navigate it: Up Next, Chat, To Do, People, and Files as first-class surfaces, not just "chat and files".

Files Page

  • Empty folders now show up in the Files page. Previously a folder with no markdown inside was hidden, which made fresh subfolders silently disappear.
  • A new File dialog creates markdown files in the current folder without dropping into chat. Naming rules match the agent's local_* tools.
  • Folder creation is now inline: hit New Folder, type the name, and the row commits like a rename. Empty-state copy mentions folders too.
  • The Files page receives granular .patinaFilesChanged notifications and invalidates only the affected file-title cache entries, so labels stay fresh when external edits land via iCloud or Finder without rescanning everything.
  • File detail view picks up the same editing affordances as the Files page so opening a file feels like a real editor instead of a viewer.

Digest Titles

  • The end-of-day digest H1 is now canonically # Digest — YYYY-MM-DD (for example # Digest — 2026-05-14). The agent's prompt, the bundled Digest skill, and the onboarding skill templates all teach this form.
  • Older digests with weekday names, month-name dates, double hyphens, or comma-only variants render with a normalized "Digest — YYYY-MM-DD" label inferred from the filename, so existing files in digests/ stay readable without being rewritten.
  • Loose ISO filenames such as 2026-5-9.md are zero-padded to 2026-05-09 when displayed.

Journal Folder

  • Daily journal entries now live directly in journal/ instead of journal/entries/. New installs no longer get the empty entries/ subfolder, and the agent's path examples use the flatter layout.

Menu Shortcuts

  • ⌘T opens New To-Do directly from the menu bar.
  • ⌘J opens Quick Journal. Both join the existing ⌘N (chat) and ⌘F (search) shortcuts.

Onboarding And Product Guides

  • The bundled onboarding guides now describe Up Next, Chat, To Do, People, and Files as the five main surfaces instead of treating Files and Chat as the whole app.
  • New installs can top up missing product guide files without re-running full onboarding, so guide updates land in existing folders too.
  • The to-do empty state and onboarding builder share consistent wording about where journal entries, digests, people files, and goals live.

Chat And Polish

  • Empty-state chat suggestion rows use a larger 12pt hover corner radius so the highlight matches the rest of the chat surface.
  • Markdown cache and renderer pick up minor reliability fixes for incremental edits.

Fixes And Reliability

  • FileTitleResolver gains invalidateAll() and invalidateChangedPaths(), so chat refresh and granular file-change events clear stale cached labels without flushing the whole map.
  • LocalFileService gains createDirectory(path:) for the new folder-create path, with the same error surface as the existing write/delete helpers.
  • Release script's notarytool profile preflight now uses xcrun notarytool history to probe for credentials instead of security find-generic-password, which no longer sees notarytool's modern profile store on recent macOS.

Tests

  • Added coverage for digest title canonicalization from canonical ISO filenames and loose two-digit date variants, including invalidation when the H1 changes.
  • Added coverage for the new folder helpers in LocalFileService.
  • Extended onboarding and system-prompt tests to lock in the journal/digest path and title changes.

Navigation And Page Polish

Patina 0.5.29 is a polish release for the everyday navigation surfaces: Files, People, To Do, and Up Next. The app should feel more consistent in dark mode, easier to scan in the sidebar, and less likely to surface starter prompts that read like tasks Patina cannot actually perform.

This release also tightens the People page. People now carry useful last-contact context directly in the list, so the page is better for triage instead of just being an alphabetical address book.

  • The sidebar now places To Do above People on both Mac and iPhone, matching the way the app treats To Do as one of the main daily surfaces.
  • Up Next now uses the same dark-mode page background as Chat, Files, and People. Its light-mode surface keeps the softer Up Next wash, but dark mode no longer reads as a different page family.
  • File rows in the Files page no longer reserve a disclosure-chevron slot. Folders open into their own page now, so removing the chevron makes file and folder row padding feel balanced.
  • The Files page sort button and list/grid segmented control now top-align in the page header, with matching control height.

People

  • The People page search field moved into the top-right header area, aligned with the page title instead of sitting below it.
  • People rows now show a last-contact subtitle such as today, yesterday, or 2 days ago.
  • The People page can now sort by last contact date as well as by name.
  • Last-contact dates are inferred from explicit Last Contact sections, dated History sections, message context, conversation context, and interaction sections.
  • Natural-language dates such as "April 15" or "May 3rd" are now recognized alongside ISO dates, so people files that use month-and-day notation surface a real last-contact subtitle.
  • Sparse person files without any dated entries now show "Unknown" rather than guessing from the file's modification or creation date, which often reflected an unrelated edit instead of an actual conversation.

Files And Header Layout

  • Files and People page headers now share the same control sizing, spacing, and padding so the sort button, view-mode toggle, and search field stay visually aligned across pages.
  • The Files page view-mode toggle uses a clearer selected-state background in both light and dark themes.

Chat Empty State

  • Empty-state chat suggestions are now plain text rather than heuristically bolding a guessed entity inside the prompt, which often emphasized the wrong word.

Starter Pills

  • Starter pills now avoid raw physical-world To Do commands such as packing, buying, paying, picking up, booking, or dropping something off.
  • When a task belongs in chat, Patina should phrase it as something it can actually help with: drafting, briefing, summarizing, searching context, planning, making a checklist, or deciding next steps.
  • The bundled morning workflow and system prompt now describe that distinction directly, so newly generated starter pills are less likely to look actionable when they are really just errands.

Fixes And Reliability

  • Added regression coverage for last-contact parsing from people files, including dated History sections that should win over unrelated dated notes, and for natural-language month-and-day dates.
  • Added starter-pill filtering coverage so unsupported commands are dropped before they appear in the prompt row.
  • Updated prompt and starter-pill tests for the new chat-capable wording.

Up Next Feedback Controls

Patina 0.5.28 makes Up Next easier to steer and tightens how Patina names people in user-facing output. The daily surface is quieter, the feedback controls are faster to reach, and dark mode text now stays neutral instead of picking up the active theme tint.

Up Next Controls

  • Up Next cards now show a compact icon row under each card blurb instead of pill buttons and a hidden three-dot menu.
  • Message Patina opens inline from the chat icon. The input expands in place across the card, keeps the send button inside the field, and returns to the icon row when canceled.
  • More like this, Less like this, Hide, and Report issue are now direct icon actions with immediate hover tooltips and pointer feedback on macOS.
  • On Your Plate cards no longer show completion checkboxes in Up Next, keeping the page focused on review, steering, and follow-up rather than task editing.
  • Card feedback now records local preferences so future Up Next meetings and open loops can be ranked toward the kinds of cards you mark as useful.

Up Next Reliability

  • Up Next filters generated open-loop blurbs and actions against recently resolved tasks, reducing stale carryover from items that have already been completed or archived.
  • Cached Up Next snapshots now reload feedback preferences and sanitize open-loop card content before display.
  • Generated card paragraphs are guarded against instruction leakage, replacing leaked internal wording with a calm fallback prompt.

Names And Sources

  • Patina is stricter about name provenance. User-facing responses, digests, and briefings should only use a real person's name when it comes from Contacts, a source-provided display name, or an existing linked people file.
  • When a message source only exposes a phone number, email, username, or handle, Patina keeps that raw identifier instead of guessing from area code, locale, carrier, domain, wording, or relationship context.
  • The bundled Digest skill is migrated for existing users so older local copies get the same verified-name rules.

Visual Polish

  • Dark mode theme text is now neutral white, with de-emphasized text using white transparency instead of a hue-tinted color.
  • Up Next cards use a dedicated elevated surface in dark mode, keeping card contrast stable across themes.
  • The queued-message bar text size is slightly larger for readability.

Tests

  • Added coverage for verified-name prompt/tool guidance, Digest skill migration, Up Next feedback ranking, stale resolved-task detection, and card paragraph sanitization.

A More Reliable Up Next

Patina 0.5.27 tightens the new Up Next surface so it behaves more like a reliable daily operating view and less like a raw dump of connected data. Meeting cards now focus on upcoming events, duplicate calendar copies are folded together, weather can be viewed in either Fahrenheit or Celsius, and card actions are constrained to things Patina can actually help with.

This release is especially noticeable if you connect more than one calendar source or keep the same event on multiple calendars. Up Next should stop showing two prep cards for the same meeting, and stale cards from yesterday should be pushed out of the way when the daily snapshot reloads.

  • Up Next now deduplicates matching calendar events after parsing. When the same meeting appears from multiple connected calendars or shared calendar copies, Patina keys it by normalized title and start time and renders a single meeting-prep card.
  • When duplicates are found, Patina keeps the richer version of the event. Records with more attendees, organizer information, notes, links, location, or calendar context win over thinner copies, so deduping should not throw away useful meeting prep context.
  • Cached Up Next snapshots now get cleaned as they load. Passed events are removed immediately, duplicate events are collapsed, and old generated meeting cards are filtered against the current event list.
  • Up Next action buttons are now sanitized before display. Patina keeps draft, summarize, context-finding, task, and planning actions, while dropping suggestions that imply unsupported side effects such as sending messages, editing calendar events, paying invoices, posting, resolving Figma comments, or mutating read-only sources.
  • If the model suggests only unsupported actions, cards fall back to safe useful defaults such as finding local context, drafting questions, refining an On Your Plate item, or deciding the next step.
  • The Up Next weather line now toggles between Fahrenheit and Celsius. Click the weather subtitle to switch units, and Patina remembers that preference locally for the home surface.

Up Next Reliability

  • Calendar parsing already rejected events that started before the current moment. This release adds regression coverage for that behavior so yesterday's meetings do not quietly reappear in the daily surface.
  • Snapshot schema versioning was bumped so older Up Next caches are rebuilt instead of preserving stale generated cards from the previous card model.
  • On Your Plate cards now reload from the canonical open-loop file rather than trusting an older snapshot copy. Completed items should stay completed after the app reconciles from disk.
  • Generated card actions are now re-sanitized when loaded from a snapshot. That keeps older cached cards from continuing to show unsupported actions after the prompt and sanitizer rules change.
  • Open-loop completion failure now triggers a disk reconciliation path, which helps when the file changed underneath the card or the specific markdown line no longer matches.
  • Weather-unit conversion handles every Fahrenheit temperature in the subtitle, including compact values such as 32F and below-freezing values.

Fixes And Reliability

  • Fixed duplicate meeting-prep cards for the same calendar event when a meeting was returned by more than one calendar source.
  • Fixed stale Up Next snapshot behavior that could keep yesterday's calendar cards visible longer than intended.
  • Fixed card actions that promised work Patina should not claim it can perform directly, such as sending emails or joining meetings. Join links still render separately when a direct URL exists.
  • Added tests for duplicate calendar events, stale calendar filtering, safe card-action sanitization, fallback actions, canonical open-loop reads, and weather temperature conversion.
  • Verified the full macOS test suite before cutting the release.

Feedback Menus For Up Next

Patina 0.5.26 makes Up Next cards easier to tune when the daily surface gets something right, gets something wrong, or simply shows something you do not want to see anymore. The old source badge in the top-right corner of each card has been replaced with a quiet three-dot feedback menu that appears on hover, keeping the card focused on the meeting or task while still giving you a direct way to steer the experience.

This is a small release, but it closes an important interaction gap from the first Up Next pass: feedback controls now visibly respond. "More like this" and "Less like this" acknowledge the click immediately, "Hide" removes the card from the current Up Next list, and "Report issue" opens the full feedback sheet with the card context already attached.

  • Up Next cards now use a hover-only three-dot menu in the top-right corner on macOS instead of showing the source app icon there all the time.
  • The card feedback menu includes four focused choices: More like this, Less like this, Hide, and Report issue.
  • More-like-this and less-like-this clicks now show an immediate acknowledgement so the control feels responsive even before those signals are used for deeper personalization.
  • Hide removes calendar cards and On Your Plate cards from the current Up Next view, so you can clear a noisy recommendation without completing or editing the underlying source item.
  • Report issue now opens Patina's existing feedback sheet instead of silently logging the click. Submitted reports include the card kind and the card context, which should make debugging bad prep, irrelevant suggestions, or confusing wording much faster.

Up Next Feedback

  • Calendar and On Your Plate cards both share the same feedback behavior, so the menu works consistently across meetings and open loops.
  • Feedback actions are logged with a structured up_next_card_feedback event that distinguishes calendar cards from open-loop cards.
  • Card issue reports are submitted through the same diagnostics pipeline as chat feedback. They can include recent logs and attach a compact card transcript without requiring the user to manually describe which card was involved.
  • The menu stays available on platforms that do not have hover, so the feedback affordance does not disappear on touch-first surfaces.

Fixes And Reliability

  • The previous source-badge path was removed from the card action slot, along with the now-unused badge plumbing inside UpNextView.
  • The feedback acknowledgement cleans itself up when the view disappears, avoiding stale transient UI if the user switches away from Up Next quickly.
  • Hidden calendar cards now clear their generated meeting-prep state and cached view model subscriptions before the Up Next snapshot is saved.
  • Hidden On Your Plate cards continue to clear their generated blurb state and snapshot entry, matching the existing behavior used after completing an item.

Messages Become Open Loops

Patina 0.5.25 makes On Your Plate better at finding the small human follow-ups that are easiest to miss. Morning Briefing and background knowledge sync now treat recent iMessage/SMS conversations as first-class open-loop sources, while still requiring verification before anything becomes a task.

This release also tightens a few rough edges from the Up Next launch: checkboxes read more clearly in dark mode, resolved items are compacted more predictably, and private inline agent work no longer risks surfacing implementation JSON as a notification.

  • Added a new imessage_reply_candidates tool on macOS. It scans recent iMessage/SMS conversations where the latest visible message is inbound, returns a compact preview, and gives agents a safer first pass for reply-debt audits.
  • Morning Briefing and onboarding skill templates now explicitly start message audits with imessage_reply_candidates, then require imessage_get_messages before deciding the user owes a response. Latest-inbound is treated as "inspect this", not proof of a task.
  • Background knowledge sync now runs a deeper open-loop audit in the early morning window and treats open-loops.md as a primary output. It checks Gmail, iMessage/SMS, Beeper, and connected task systems, then adds or resolves canonical items silently.
  • Reply debt can now be tracked even when it is undated, as long as a recent direct conversation clearly shows the user still owes a response, decision, reassurance, or follow-up. Dated commitments still need a clear date or date window.
  • Archived Gmail is now called out more firmly as resolved. Agents are instructed not to add archived email threads to On Your Plate unless a later source creates a new user-owned action.

Fixes And Reliability

  • Gemini empty-response retries now append a hidden continuation nudge before retrying. This gives Gemini a clearer recovery path instead of simply replaying the same request after an empty response.
  • Private inline ChatViewModel instances, such as the ones used to generate Up Next card context, no longer post Mac or iOS notifications. Only real live chat threads can produce notification deeplinks.
  • Checked task boxes now use a white fill with a dark checkmark in dark mode, both in the SwiftUI task checkbox and the selectable markdown reader. This keeps completed tasks legible across themes.
  • On Your Plate scans now compact a legacy ## Resolved section into ## Recently Resolved, adding resolution timestamps where needed and keeping the active list focused.
  • Opening open-loops.md in the file detail view runs the same resolved-item compaction so the visible file catches up even outside the Up Next flow.
  • The Up Next weather subtitle now uses filled SF Symbols for condition icons, matching the denser visual language of the cards.
  • Tool call status text now gives the iMessage reply-candidate scan its own label: "Checking who may need a reply."

Website

  • The marketing homepage weather preview now uses reusable filled weather symbols instead of repeated stroked inline SVGs. The visual result is closer to the app's current Up Next weather treatment and keeps the markup easier to maintain.

Tests

  • Added regression coverage for the iMessage reply-candidate tool registration and macOS-only tool handling.
  • Added coverage for Gemini's empty-response retry nudge.
  • Added coverage to ensure private chat view models do not send notification deeplinks.
  • Added coverage for legacy resolved-section compaction in On Your Plate.
  • Updated prompt and system guidance tests around archived email handling, reply debt, Beeper message limits, and open-loop audit behavior.

Up Next Becomes The Daily Surface

Patina 0.5.24 replaces the old Home dashboard and Recent Activity surface with a more focused Up Next experience. Instead of making you manage widgets or scan a feed of everything that happened, Patina now opens on the next few things that actually need attention: meetings to prepare for, timely open loops, weather context, and direct actions that can turn into a chat.

This release is about lowering the amount of interface you have to negotiate before the app becomes useful. Calendar context, On Your Plate, source links, and meeting prep now come together in one quieter view that is easier to read at the start of the day.

  • Up Next is now the primary first screen. It shows the current date, local weather, the next relevant meetings, and unresolved items from On Your Plate without requiring a configurable Home layout.
  • Meeting cards now generate compact prep summaries and suggested actions. You can start a new chat directly from a card, and Patina carries the card context into the conversation so the first reply is grounded in the meeting or task.
  • On Your Plate items now appear as actionable cards when they matter for the current day. Completing an item from Up Next updates the underlying open-loops file, then removes the card from the view after a short confirmation window.
  • Source awareness is much stronger. Open loops can carry exact source URLs, source app markers, timestamps, Beeper channel kinds, Gmail links, and calendar links. The UI uses those markers to show the right source badges and to keep task titles clean.
  • The sidebar and Up Next background now share the same visual surface on macOS, so the window no longer has a mismatched strip behind the sidebar when Up Next is selected.
  • The Home widget system, Recent Activity feed, hourly activity sync, and related background machinery have been removed. Patina now spends less time maintaining an activity timeline that was easy to overfit and hard to trust.

Integrations And Context

  • Google Calendar output now includes event end times, source markers, event URLs, organizers, attendees, calendar color metadata, and source identifiers. Up Next uses that richer structure to separate meeting timing from prep context.
  • Gmail search and message reads now expose stable Gmail web URLs when possible, including the account hint needed to open the right mailbox. Those URLs can be stored as source_url on open loops so a task can point back to the exact email that created it.
  • Beeper activity candidate handling was tightened so message-derived work can be deduplicated by source item rather than by fuzzy token overlap alone. This should reduce both repeated tasks and missed tasks when similar conversations happen close together.
  • The system prompt and onboarding skill templates now teach agents to pass source_url whenever a tool result provides an exact link. That improves traceability without making the visible task title longer.
  • File source notes now understand source markers, source URLs, event kinds, thread markers, timestamps, and Beeper channel families. This gives Up Next a single place to resolve logos, accessibility names, and source metadata.

Fixes And Reliability

  • The app no longer runs the removed Home activity sync path in the background. That cuts down on unnecessary work and removes a class of migrations and feed updates that could make the app feel busy without producing a clearer daily view.
  • Open loop parsing and completion now preserve client, project, source, URL, and timestamp metadata while still displaying concise titles. Resolved items are matched more leniently so checkboxes and structured completion tools stay in sync.
  • Markdown rendering and selectable file reading received smaller compatibility fixes around attributed text, source-note stripping, and task checkbox behavior.
  • Environment compatibility checks now cover the current macOS runtime assumptions for released builds.
  • Regression coverage was updated around Up Next open-loop behavior, background sync retirement, task checkbox writeback, source URL handling, connection reconciliation, and prompt guidance.

Smarter Daily Synthesis

Patina 0.5.23 upgrades the model routing behind the daily life-insight workflows so the parts of the product that require judgment now run on smarter models while background maintenance stays cost-conscious.

The goal of this release is trust. Morning Briefing, Daily Digest, Weekly Review, and Meeting Prep are the moments where Patina turns raw context into an interpretation of your life. Those workflows now use the strongest model available for the selected provider, so the app spends intelligence where mistakes would be most costly.

Morning Briefing, Daily Digest, Weekly Review, and Meeting Prep now route through a workflow-aware model router. Claude users get Opus for those high-trust synthesis runs, Gemini users get Gemini Pro, and OpenAI users get GPT-5.2. Normal chat keeps using the user-selected model, so switching daily workflows to a stronger model does not make every conversation expensive.

Knowledge sync also moves to a stronger model tier and now runs once per day in the evening instead of multiple times per day. The intended shape is fewer automatic synthesis passes, better reasoning per pass, and a similar cost envelope for users who leave background sync enabled.

Background Sync

Knowledge sync is now scheduled once daily at 9 PM. The intended shape is fewer automatic synthesis passes, better reasoning per pass, and a similar cost envelope for users who leave background sync enabled.

Model Catalog

Claude Opus 4.7 is now available in the Claude model catalog and is preferred for the strongest Claude synthesis workflows. Gemini 3 preview models are present behind the experimental-models flag so they can be tested without becoming the default stable choice.

Backend cost-estimation fallback logic also recognizes the newer Opus pricing path, so cost reports stay aligned when Opus-class models are used.

Reliability And Tests

This release adds regression coverage for the model router, workflow-to-model mapping, experimental Gemini visibility, Claude Opus ordering, and the once-daily knowledge sync schedule.

A Better First Dashboard

Patina 0.5.22 refreshes the first Home dashboard people see after onboarding so the first-run surface feels more balanced and useful.

The headline change is the new default Home layout. New setups now open onto a dashboard shaped around the day: the date and weather sit together at the top, calendar context and open loops stay prominent, and the prompt dock is wider by default.

Home Dashboard

The post-onboarding Home dashboard now starts with Day, Weather, Up Next, and On Your Plate visible in a balanced two-column layout. Day and Weather each span half the top row, Up Next anchors the left column, and On Your Plate sits below Up Next.

This only changes the built-in default manifest for users who have not customized their Home widgets yet. Existing saved dashboard layouts remain respected through .patina/home-widgets.json, so people who already arranged or hid widgets should not have their Home surface reset by the update.

Reliability And Tests

This release adds regression coverage for the new default Home widget order, visibility, and grid spans so the post-onboarding dashboard does not drift accidentally.

Home And Automation Reliability

Patina 0.5.21 is a Home and automation reliability release. It makes scheduled skills clean themselves up more predictably, keeps background sync state fresher, and polishes the Home dashboard so the app feels steadier on both Mac and iPhone.

This update is especially useful if you rely on recurring skills, the Home dashboard, or quick search to move between chats and files. The changes are mostly quiet by design: fewer stale background agents, clearer account states, and a more natural mobile command search experience.

  • Scheduled skills now remove old launchd jobs more aggressively, including orphaned automation plists left behind by older automation ids.
  • Home dashboard data stays fresher after background syncs, with clearer calendar permission messaging.
  • Mobile Home and command search have been polished with a bottom-sheet search presentation, better page spacing, and iOS-friendly calendar filtering.
  • File navigation and quick search on iOS handle focus, keyboard dismissal, and recent-file promotion more cleanly when moving between Home, Files, and Chat.

Automation And Background Sync

Patina now rechecks the launchd agents it owns whenever automations are synchronized. If an automation was deleted, renamed, or left behind by a previous build, the app can remove the stale plist instead of keeping a dead background schedule around in ~/Library/LaunchAgents.

The automation runner and background sync paths also received several smaller reliability updates. Scheduled skills continue to route through Patina's managed app runtime, but the surrounding state is less likely to drift from the user's visible automation list.

For users, the intended result is simple: deleting or changing a scheduled skill should stick, background activity should match what Settings shows, and old automation entries should not keep waking the app after they are gone.

Home Dashboard

The Home dashboard now reacts better to synced notes, open loops, and account state changes. The dashboard model now has more test coverage around changed paths and open-loop updates.

Calendar status messages are also clearer. When Google Calendar access is missing because the account lacks calendar permission, Patina now explains that Gmail may still be connected and points users toward reconnecting Google with calendar access. Temporary calendar availability problems are phrased less harshly so they do not look like a permanent account failure.

The calendar filter also behaves better across platforms. On Mac it keeps the compact popover workflow, while iPhone uses a sheet with toggles and a Done action that fits the platform.

Mobile Polish

Command search now presents as a bottom sheet on iPhone instead of trying to reuse the Mac overlay. That makes search feel more native on smaller screens and avoids keyboard and focus oddities while navigating between chats, files, and the Home dashboard.

The iOS Home surface also gets tighter layout handling: dashboard padding is more consistent, the prompt input can adapt its focused width, and file navigation is less likely to summon the chat keyboard after the user backs out of a file.

Calendar Reliability

Patina 0.5.20 is a focused calendar reliability release. It fixes the noisy Up Next failure that could appear after connecting another Google account, especially a personal Gmail account with shared, subscribed, or Google-owned calendars attached.

The bug was not that Gmail itself was broken. Patina was successfully discovering Google calendar accounts, then fetching events calendar by calendar. If one selected secondary calendar produced a Google 400 Bad Request response, the Home dashboard could surface Google's raw error JSON instead of showing upcoming events or a calm status message. That made the Up Next widget look much more broken than the underlying issue actually was.

  • Up Next is more resilient when a Google account includes shared, subscribed, holiday, contact, or otherwise unusual calendars.
  • Google calendar ids are now encoded as URL path segments before event fetches, so reserved characters in calendar ids cannot reshape the request URL.
  • Broken secondary Google calendars no longer take down the whole account's Up Next result.
  • Home now shows a short calendar-unavailable message instead of dumping raw Google API JSON into the widget.

Google Calendar Reliability

Patina now treats the account's own primary calendar differently from secondary calendars. If the primary calendar fails, that is still surfaced as a real Google Calendar availability problem. If a secondary calendar returns a skippable 400 or 404, Patina logs it and keeps loading the rest of the account's calendars.

This keeps the dashboard useful in the common case where one imported or subscribed calendar is malformed, unavailable, or has an id shape that Google accepts in the calendar list but rejects during an event fetch. Users should still see events from the remaining calendars instead of a full-widget error.

User-Facing Fixes

  • Fixed the Up Next widget showing repeated Google Calendar returned HTTP 400 errors after adding another Google account.
  • Fixed calendar event fetch URLs for ids containing characters such as @, #, or /.
  • Reduced raw backend/API noise in the Home dashboard when calendar access genuinely needs attention.
  • Preserved existing multi-account behavior: when several Google accounts have usable calendars, their events continue to merge into the same upcoming-event list.

Cost Control For Background Work

Patina 0.5.19 is a cost-control release. It keeps automatic background help turned on, but makes the most expensive low-value paths calmer so light users do not pay for a lot of invisible work.

The main change is that background work is easier to attribute and less likely to spend on repeated low-value requests.

  • Chat titles use local, deterministic titles first for short conversations, so quick one-message threads no longer need repeated model calls just to rename themselves.
  • Longer conversations can still ask the model for a better title when there is enough context to make that extra request worthwhile.
  • Backend usage logging now separates hidden sync work from normal chat usage, making it easier to see where background cost is coming from.

Cost And Usage Improvements

  • Short or simple chats now settle on a local title quickly instead of retrying the remote title summarizer several times.
  • Hidden sync runs now carry a run id through usage logging, so future audits can distinguish knowledge sync and regular chat traffic.

Fixes And Reliability

  • Sync usage attribution is covered by backend tests so hidden Gemini sync calls continue to be counted under the right workflow.
  • The macOS test runner path is more resilient on machines where LaunchServices refuses to launch the hosted test app even though the test bundle itself is valid.
  • Thread migration tests now pin and restore the model defaults they depend on, which avoids leaking user defaults between tests.

Apple Calendar In Up Next

Patina 0.5.18 finishes the Apple Calendar path on Home by bringing Apple Calendar events into the Up Next widget. If your day is split between iCloud, subscribed Apple calendars, and Google Calendar, Home now has one combined view instead of leaving Apple Calendar events available only through chat and briefings.

This is a small release, but it closes an important everyday gap from 0.5.17: connecting Apple Calendar now affects both the agent tools and the dashboard surface people check first.

  • Up Next now reads Apple Calendar whenever the Apple Calendar connection is enabled.
  • Apple Calendar and Google Calendar events are merged into the same upcoming-event list, sorted by event time, so the next thing on your calendar wins regardless of which provider owns it.
  • Apple calendar names and colors are loaded into the widget's calendar source list, making local, iCloud, shared, holiday, family, and subscribed calendars easier to distinguish.

Fixes And Reliability

  • The empty Up Next state now says to connect a calendar generally instead of pointing only at Google Calendar.
  • If Google Calendar needs reconnecting but Apple Calendar still has usable events, Up Next can continue showing the Apple events instead of failing the whole widget.
  • Apple Calendar source metadata is fetched through the same EventKit access path as events, so the calendar picker can use the system's current calendar list and colors.
  • The Home dashboard keeps its existing Google Calendar behavior while adding Apple Calendar as a parallel source, avoiding a migration or preference reset for existing Google users.

Apple Calendar

Patina 0.5.17 brings Apple Calendar into the app as a native local source, gives the Home calendar widget more control, and smooths a handful of first-run, file-reading, and connection edges. The release is especially useful if your schedule lives partly in iCloud or the macOS Calendar app, or if you rely on Home as the quick daily view of what is happening.

If you're on 0.5.16, install 0.5.17 directly. Existing chats, files, connections, subscriptions, automations, and synced Home data carry over.

Patina can now connect to Apple Calendar through the system calendar permission on macOS. Once connected, Patina can read iCloud calendars and any other calendars visible in the Calendar app, then use them in briefings, digests, schedule questions, and upcoming-week checks.

The new Apple Calendar tool returns event times, titles, locations, calendar color metadata, attendees when available, and common video meeting links. It sits alongside Google Calendar, so users with both sources connected can still ask natural schedule questions without having to remember where each event lives.

The Connections screen now includes Apple Calendar health checks, and the app ships the required calendar usage description and entitlement so the system permission prompt appears cleanly.

Up Next Calendar Control

The Up Next widget now has a compact three-dot calendar picker. It stays tucked into the widget chrome at rest, then opens into a popover with grouped calendar rows, color swatches, checkmarks, and a stable Show All Calendars action.

Turning a calendar off hides its events from Up Next; turning it back on restores those events immediately. Patina keeps the full upcoming event window in memory, applies the selected-calendar filter, and then picks the next visible events, so hiding one noisy shared, family, work, or subscribed calendar reveals the next relevant events instead of leaving empty slots.

Calendar choices are saved locally. For people with multiple Google accounts connected, calendar labels include the account email where useful, making similarly named calendars like Personal, Work, Family, Holidays, and Birthdays easier to tell apart.

Home Dashboard

The weather card now gives a clear next action when Patina cannot use your current location. If location access has not been decided yet, Home can ask for permission from the card; if access was denied or restricted, it offers a direct path to system settings. Profile-location fallback still works for users who prefer not to share device location.

Files And Markdown

File detail pages now show a last-updated subtitle under the document title, so it is easier to tell whether an iCloud or background-sync update has landed.

The markdown renderer gets a spacing pass for headings, paragraphs, and compact metadata blocks.

Chat blockquotes now render as normal text instead of visually demoting draft copy with a quote rail. That keeps generated options and reusable snippets from looking like quoted evidence when the agent is simply drafting language for you.

First-Run And Connection Reliability

The onboarding handoff chat now materializes in the sidebar immediately, with a stable "Getting started" title and a running state while the hidden setup prompt is thinking. That prevents the first setup conversation from becoming hard to find if you navigate away before the hidden turn finishes.

Slack MCP OAuth token responses are now parsed in Slack's authed_user shape, including user access and refresh tokens. Slack error responses with ok: false surface the actual Slack error code instead of a generic malformed-response message.

Full Disk Access priming now touches a stable system TCC probe before the user TCC database and includes an additional Time Machine protected path. This gives macOS a better chance to show Patina in the Full Disk Access list before the user opens System Settings.

Fixes And Reliability

  • Apple Calendar is exposed through connection setup tools, status tools, the agent prompt, and the client tool cache.
  • Up Next event filtering preserves join links, locations, all-day labels, day grouping, meeting-prep actions, and deterministic fallback calendar colors.
  • Empty calendar states now distinguish between "nothing on the calendar" and "no events on selected calendars."
  • Calendar title shortening no longer leaves dangling connectors like "and", "&", "+", or "@" at the end of Home prompt pills.
  • Markdown rendering is covered for readable headings, last-updated subtitles, and adjacent metadata line spacing.
  • Onboarding handoff materialization is covered so hidden setup turns keep a visible sidebar row.
  • Slack MCP OAuth parsing is covered for both successful authed_user responses and ok:false token errors.

Quieter Chat Transcripts

Patina 0.5.16 makes busy chats calmer, gives the agent a first-class web search tool, and tightens several places where the app could feel noisy or slightly lost. The biggest visible change is in the transcript: Patina's intermediate progress messages now collapse into a single "Thought for N secs" disclosure, while the final answer stays readable.

If you're on 0.5.15, install 0.5.16 directly. Existing chats, files, connections, subscriptions, automations, and synced Home data carry over.

Long research turns used to leave every interim note and every tool group sitting inline above the answer. That was useful for trust, but it made the actual answer harder to find, especially when Patina searched email, messages, calendar, and files before replying.

0.5.16 collapses the assistant's working phase after each user message into one expandable row, labeled with the elapsed time when event timestamps are available. Opening that row shows the same tool-call and progress detail as before, without changing the alignment of the transcript. Tool-call labels and thought labels now use the same regular text weight as assistant messages, so the scaffolding reads as context instead of competing with the answer.

Tool call details are still available. They are just tucked away until you ask for them.

Web Search As A Patina Tool

Patina now exposes web_search as a client tool for current public web information: latest news, pricing, product details, company facts, and other things that can change after the model was trained. The backend route uses Gemini's Google Search grounding, returns source URLs, logs usage, and respects the same subscription and fair-use checks as the rest of Patina's hosted AI features.

This lets providers that already use custom tools ask the web without needing native web-search support mixed into the model session. The system prompt now tells the agent when to reach for web search, and the tool schema supports an optional site/domain constraint for targeted lookups.

Notification Taps Route To The Right Thread

On macOS, tapping a Patina notification could wake the app but leave you on Home or on the previously selected chat. 0.5.16 persists the tapped thread id before posting the in-app deeplink, brings the main window forward, and routes the top-level content view into chat mode so the target thread is actually selected.

Morning briefing and digest notifications also get clearer copy. Instead of surfacing a raw skill name or a slash-command-looking trigger, they say that the dated briefing or digest is ready and invite you to open Patina.

Cleaner Home State

The Home header now shows a small progress indicator while background syncs, skill runs, or active threads are doing work, instead of presenting a refresh button that looks immediately actionable.

Provider And Session Safety

Patina now checks that stored session ids match the selected provider before restoring or switching to them. Gemini session ids will not be routed through Claude Code, OpenAI session ids will not be routed through Gemini, and legacy unknown ids are preserved for older installs instead of being silently orphaned.

The agent version was bumped so non-Claude Code sessions refresh when needed and pick up the latest tool and prompt surface, including web search.

Website Polish

The marketing site spacing around the activity, plate, and people sections was adjusted for a more balanced scroll rhythm on desktop and mobile. The download modal copy was simplified, and older unused marketing images were removed from the site bundle.

Fixes And Reliability

  • Chat thought duration uses backend event timestamps when available, so elapsed labels reflect the actual run instead of view render timing.
  • web_search is covered by backend tests for Gemini grounding, source extraction, subscription gating, usage logging, and client tool schema availability.
  • Notification deeplink routing is covered by regression tests for pending thread persistence, top-level handling, chat-mode switching, and briefing/digest notification copy.
  • Provider/session compatibility has tests for Gemini, OpenAI, Claude Code, Claude BYOK, and legacy session id behavior.

Focused Calendar Context

Patina 0.5.15 tightens several Home dashboard and Files details. Calendar context stays focused, app icons stay consistent between Home and Files, and repeated release/build updates from the same thread collapse into a single summary.

If you're on 0.5.14, install 0.5.15 directly. Existing chats, files, connections, subscriptions, and automations carry over.

Calendar Context Stays Focused

Calendar data continues to power the Up Next widget and calendar questions in chat without duplicating already-attended events in other Home surfaces.

Release Bursts Collapse Into One Update

0.5.15 adds a compaction pass for obvious release/TestFlight/App Store review bursts. When several bullets come from the same source and describe the same release flow, Patina now summarizes the net change in one bullet instead of showing every intermediate step. For example, a long run of build-review chatter becomes a single "moved version X through the TestFlight/App Store review flow" style update with the useful details preserved.

Source Icons Match Between Home And Files

Home and Files now share more of the same icon resolution behavior. The Files view used to render Markdown first and then try to match the rendered line back to the raw marked line. Apple's Markdown renderer smartens punctuation, so Leah's became Leah’s, the exact match failed, and the Files view fell back to the generic Patina icon.

The Files reader now normalizes smart quotes, smart dashes, and ellipses before matching source hints. Files should show the same app icons you saw on the Home dashboard.

Fixes And Reliability

  • Source marker matching in the Files reader survives smart punctuation.
  • Release/TestFlight/App Store review burst compaction is covered by regression tests.

Known Notes

The macOS test bundle can fail to launch on this machine with Xcode LaunchServices error code 20 when a Patina test host is already wedged. A Debug build was verified before release; the release pipeline's packaging, signing, notarization, Gatekeeper, appcast, and GitHub release checks still run end to end.

Knowledge Sync Checkpoints

Patina 0.5.14 is a quick follow-up to 0.5.13 that tunes the full knowledge sync cadence. The goal is the same: keep the knowledge base fresh enough for the day without letting the heavier agent workflow run more often than it needs to.

If you're on 0.5.13, install 0.5.14 directly. Existing chats, files, connections, subscription state, and automations all carry over.

Full Knowledge Sync Runs At Five Checkpoints

The full knowledge sync is the expensive background pass: it asks the agent to reconcile people, clients, identity, digests, and connected sources. In 0.5.13, we moved it out of the overnight quiet window, but the daytime cadence was still too aggressive for the amount of work that pass performs.

Patina now starts the full knowledge sync only during five local-hour checkpoints:

  • 12:00 AM
  • 6:00 AM
  • 12:00 PM
  • 6:00 PM
  • 9:00 PM

Those checkpoints are intended to cover the moments when a deeper refresh matters most: a final sweep around midnight, a pre-morning-briefing refresh, a midday catch-up, an end-of-workday refresh, and an evening pass. Between those windows, Patina leaves the heavier workflow alone.

The result should be a better cost/benefit split: deeper knowledge maintenance at a handful of predictable checkpoints instead of frequent broad reconciliation.

Queued Syncs Can Finish Naturally

Queued knowledge-sync triggers are no longer blocked by the delivery clock. If Patina queues a full sync during a scheduled window and the inbox worker picks it up a few minutes later, it can still run. This avoids the awkward case where a 6:55 AM sync waits until noon just because delivery happened after the top of the hour.

Manual syncs are still manual. If the user explicitly asks Patina to sync, the app can queue that work regardless of the automatic schedule.

Tests

The background sync tests now pin the new five-checkpoint schedule. They confirm the full knowledge sync is due at midnight, 6 AM, noon, 6 PM, and 9 PM, and paused in the hours between.

Knowledge Sync Sleeps Overnight

Patina 0.5.13 is a quiet reliability release for background knowledge sync. The full knowledge refresh now respects an overnight rest window: it stops starting at local midnight and resumes at 6:00 AM, giving the Mac a calmer night without leaving the morning briefing stale.

If you're on 0.5.12, install 0.5.13 directly. Existing chats, files, connections, subscription state, and automations all carry over.

The full knowledge sync is Patina's heavier background pass. It asks the agent to refresh the knowledge base across people, clients, identity, digests, and connected sources. That is useful context during the day, but it does not need to start new runs in the middle of the night.

Automatic knowledge sync now checks the user's local wall clock before it decides a due run can start. From 12:00 AM through 5:59 AM, Patina treats the knowledge sync as paused. At 6:00 AM, the normal due check resumes. If the sync was already due overnight, it stays due and runs once the window opens instead of being marked skipped or forgotten.

Queued Knowledge Runs Wait For Morning Too

There is a second path where the app can already have a hidden knowledge-sync trigger on disk before midnight, but the inbox worker has not delivered it to the agent yet. That queue now observes the same overnight rule. During the pause window, queued knowledge triggers remain in place and are picked up after 6:00 AM.

That makes the behavior easier to reason about: "knowledge sync is paused overnight" means both no newly-scheduled automatic run and no late delivery of an already-queued hidden run. Manual syncs are still available when the user explicitly asks for them.

Morning Briefings Stay Fresh

The resume time is intentionally 6:00 AM rather than 7:00 AM. Morning briefings commonly run around 7:00 AM, and they read the files Patina maintains. Resuming knowledge sync at 6:00 AM gives the refresh a runway before the briefing kicks in, instead of making both agent workflows compete at the same time.

The sync's normal lookback still covers overnight events. Resuming at 6:00 AM does not drop midnight-to-morning context; it simply delays the heavier knowledge maintenance until the morning window opens.

Tests

The background sync tests now pin the exact quiet-hour boundaries: knowledge sync can run at 11:59 PM, stops at 12:00 AM, remains paused at 5:59 AM, and resumes at 6:00 AM.

A Cleaner Sign-In Flow

Patina 0.5.12 is a feature release. The sign-in flow gets its own reusable screen, calendar handling gets calmer, and Debug builds stop fighting with the released app over macOS TCC permissions.

If you're on 0.5.11, install 0.5.12 directly. Existing chats, files, connections, subscription state, and chat checkbox toggles all carry over.

Sign-In Flow Extracted to Its Own Screen

The email + 6-digit-code sign-in code lived inline in WelcomeStep, which made it hard to reuse from other surfaces (Settings, deep links). The flow now lives in a standalone MagicCodeSignInScreen that handles the whole loop — request the code, accept the input, retry, surface error states. WelcomeStep loses ~270 lines and AuthService glue isolates cleanly from layout chrome.

Threads List Refactor

ThreadsListView is split into a standalone ThreadRow view that renders the per-row content, with caller-supplied selection chrome and optional menu callbacks (onTogglePin, onRename, onDelete). Same row can render in both the source-list sidebar and the command-palette results without duplicating visuals. The source-list now consumes ThreadRow directly through ContentView.

Debug Builds No Longer Fight With Production Over Permissions

macOS TCC (Full Disk Access, Accessibility, etc.) ties grants to bundle id + code requirement. The shipped Patina is Developer ID signed; Xcode Debug builds are Apple Development signed. Same bundle id + different signing identity meant System Settings would show both as one unstable "Patina" row that flipped permissions based on which build last launched.

Debug builds now ship under their own bundle id (com.gabrielvaldivia.patina.debug) with the display name "Patina Debug". Release builds are unchanged. TCC sees them as two separate apps and grants stay stable. Only relevant if you build from source.

Smaller Polish

  • Staggered startup: app launch sequence spreads its initial work across phases instead of doing everything in the first runloop tick. Smoother first-paint, no perceptible difference once warm.
  • AutomationService follow-ups on the durable-trigger work that landed in 0.5.11; tests grow assertions for them.
  • ChatTextField, SwipeToDelete, AddConnectionSheet, OnboardingPreviewWindow: minor interaction polish.

Scheduled Skills Survive Launch Hangs

Patina 0.5.11 is a stability release. Scheduled skill briefings (morning, digest, sync-knowledge) survive a hung app launch, the Gemini provider stops getting confused by late-arriving tool results, and the threads list ellipsis button reads cleanly on every theme.

If you're on 0.5.10, install 0.5.11 directly. Existing chats, files, connections, subscription state, and chat checkbox toggles all carry over.

The launchd runner script for each scheduled skill used to wake Patina first via open, then rely on InboxWatcher to discover the skill that needed to run. If the launch hung (LaunchServices wedged, app was mid-update, etc.), the trigger never landed on disk and the next Patina launch produced a blank chat thread instead of the expected briefing.

The runner now writes the slash-command trigger into .patina.nosync/inbox/<skill>-<utc>.md *before* touching LaunchServices. If launch hangs or fails, the durable trigger file remains on disk and the next Patina launch picks it up through the normal InboxWatcher scan — your morning briefing isn't lost just because launchd had a bad day.

Each launch invocation (launchctl kickstart, open) now runs through a small run_with_timeout helper (5–10s) so a wedged LaunchServices can't pin the launchd job indefinitely. Job returns exit 124 if the launch can't complete, but since the trigger is already on disk, the briefing still runs whenever the user does manage to open the app next.

Two source-grep tests pin the new ordering and the timeout-bound shape so a future refactor can't silently regress to the old launch-then-write race.

Gemini: Drop Stale Tool-Result Deliveries

Multiple late tool-result deliveries (a leftover from a previous turn, or a duplicate retry from the MCP bridge) could land while the session was already running its next turn, corrupting the record's status and unresolvedToolCalls map. Symptoms ranged from "tool call shows as still running but the model already moved on" to outright duplicate model calls.

Two guards on the deliver path:

  • Skip the delivery entirely if the session is already .running — late results have nothing to attach to and would only confuse the next batch's accounting.
  • Skip delivery when the toolUseId no longer appears in the current batch's unresolved set — that's a stale id from a closed-out batch.

Replaced the "all results received" gate with an explicit currentToolBatchIsComplete(unresolvedToolCalls:pendingToolOutputs:) helper that compares ids set-wise instead of by count, so a stray pending output for a non-current id can't fool the gate into firing a batch that's actually still missing results. And if the model call fails on retry, the session record is restored to .waiting in a defer-style block so an exception can't leave the record stuck on .running forever.

Two new pinning tests cover the batch-completion contract.

Thread Row Ellipsis Reads on Every Theme

The per-row ellipsis menu button in the threads list rendered with the system .secondary color, which on darker themes washed out against the row background. Switched to .primary (with a matching .tint(.primary) on the menu itself) so the affordance is legible no matter which theme you're on.

Activity Files Use Your Local Day

Patina 0.5.10 is a polish release. Activity files now roll over with your local day instead of UTC, the home weather widget gains a persistent °F / °C toggle, launch no longer spawns a fresh chat thread when the previous one is more than 2 hours old, the macOS chat surface picks up attribute-only changes (strikethrough) without losing selection, and the file reader's rendering tightens up.

If you're on 0.5.9, install 0.5.10 directly. Existing chats, files, connections, subscription state, and chat checkbox toggles all carry over.

Date-sensitive Home data now uses the user's local zone (TimeZone.autoupdatingCurrent) so late-evening updates stay attached to the calendar day you're living in.

Weather Widget Temperature Unit Toggle

The home dashboard's weather widget always rendered Fahrenheit. Tap the temperature on the widget (or any of the displayed values) to toggle between °F and °C; the choice persists across app launches via AppStorage("homeWeatherTemperatureUnit"). The hourly strip, "today's high/low", and "feels like" all switch units together — no half-rendered state. Accessibility-hint copy describes the toggle for screen readers.

Launch No Longer Spawns Surprise Chat Threads

The 2-hour launch rule used to do two things: if the user touched a thread within 2h, switch to it; otherwise, spawn a fresh chat thread automatically. The second half meant launching Patina after a long break could silently create an empty chat row in your sidebar even when you weren't planning to start a conversation.

Launch now only restores the recent active-thread pointer. Stale or missing pointers are ignored — the home dashboard or the most-recent existing thread takes over instead, and no new thread is created until you explicitly start one. Renamed applyLaunchRule to restoreRecentActiveThreadOnLaunch to reflect the smaller scope, and updated LaunchRuleTests to pin the new contract.

macOS Chat Strikethrough Reliability

The macOS SelectableText update path checked tv.textStorage?.string != newNS.string — only the underlying string. Attribute-only changes (strikethroughStyle, foregroundColor) slipped through and the NSTextView never re-rendered, so checking off a chat task didn't always strike through the body text on the Mac. Same pathology the iOS path hit during the 0.5.7 cycle.

Switched the comparison to NSAttributedString.isEqual(to:) so it considers both string and attributes. Small tradeoff: an attribute-only change now invalidates an in-flight text selection if you're mid-drag, but attribute updates are rare and losing selection on a checkbox toggle is acceptable. Strike lands without forcing a remount.

File Reader Rendering Tweaks

SelectableMarkdownReader (the macOS file viewer) picks up small layout improvements — paragraph spacing, list-line indent, heading typography — that bring the file pane closer to the chat surface's rendering. Same content reads more comfortably on either side.

Smaller Cleanups

  • MCPOAuthService reconciliation tightening so a connection's stored token state stays consistent across launches even when the OAuth provider returns partial responses.
  • Sidebar + menu-bar surface gain small behavioral fixes that smooth out edge cases (active-thread pointer freshness, onboarding preview sourcing).
  • New tests in LaunchRuleTests, ConnectionStateReconciliationTests, and SyncServiceBackgroundTests pin the new behavior.

Internal: Wrangler Token Now in Keychain

Not user-facing, but worth noting for future-me: release.sh step 7 now reads a Cloudflare API token from the login keychain (service patina-cloudflare-api-token) and exports it as CLOUDFLARE_API_TOKEN before invoking wrangler. The OAuth-token-expiry retry path that tripped three releases in a row in May 2026 is gone — the keychain entry doesn't expire unexpectedly, and a missing entry fails fast with the exact security add-generic-password command needed to fix it.

Date-Based Starter Pills

Patina 0.5.9 is a polish release. Starter pills can be gated to a specific calendar date instead of just a time-of-day, automation scripts are easier to recognize in Activity Monitor, and several Home/dashboard edges get smoother.

If you're on 0.5.8, install 0.5.9 directly. Existing chats, files, connections, subscription state, and chat checkbox toggles all carry over.

The starter-pill parser only understood HH:MM time-of-day gates, so a date-bound prompt like "Pay NYSEG eBill @until 2026-05-28" fell through as an unrecognized @word token and showed the literal @until 2026-05-28 in the prompt text.

@after and @until (and @between) now recognize YYYY-MM-DD forms too. Date components are evaluated as wall-clock midnight in your local zone, so "Pay NYSEG eBill @until 2026-05-28" is visible through end-of-day on 2026-05-27 local and disappears at midnight 2026-05-28.

```md

  • Pay NYSEG eBill @until 2026-05-28
  • Renew domain @until 2026-06-15
  • Tax filing reminder @after 2026-04-01
  • ```

Launchd Runner Scripts Show the Patina Icon

Each Patina automation (/digest, /morning, /sync-knowledge, etc.) lives as a small patina-run-*.sh file under ~/Library/Application Support/Patina/runners/. Activity Monitor and Finder used to render those rows with the generic shell-script glyph. The app now stamps the Patina app icon onto each runner script via NSWorkspace.setIcon, so when Activity Monitor surfaces "patina-run-digest.sh" you can immediately recognize it as Patina rather than a stray script.

Smaller Cleanups

  • Onboarding starter-pill suggestions pick up the new @until YYYY-MM-DD syntax in their templates and explanatory copy.
  • CLAUDE.md and the in-app PromptSuggestions doc-comments updated to describe the new shape.
  • Four new pinning tests cover the date-pill gate, the runner-icon path, and related setup paths so a future refactor that drops them fails the test instead of silently regressing.

Pinned Chats And Files

Patina 0.5.8 reshapes the sidebar around pinned chats and pinned files, replaces the command palette's "Chat - 3h" subtitle with the actual last message, and fixes the file-browser hover jitter. Mostly UX polish, but each piece touches a place you spend real time in.

If you're on 0.5.7, install 0.5.8 directly. Existing chats, files, connections, subscription state, and chat checkbox toggles all carry over. Pinned state for chats and files starts empty.

Pin Chats and Files in the Sidebar

The sidebar gets a Pinned section above Chats. Right-click any chat row in either the Home sidebar or the dedicated Chats sidebar to Pin / Rename / Delete. Hovering a chat row swaps the time on the right ("4h", "now") for a 3-dot menu that opens the same actions. File pinning works from the Files tab — right-click a file row for the same Pin / Rename / Delete menu, and the pin shows up immediately in the sidebar's Pinned section without a tab switch.

Pinned chats and pinned files render together in one Pinned section, in the order you pinned them. Pinned chats are filtered out of the regular Chats list so a thread never shows in two places at once. Renaming a pinned file carries the pin across; deleting drops it.

State persists per Patina folder: thread pin state lands in .patina/index.json (with a backward-compat decoder so existing index files keep loading), and file pins land at .patina/pinned-files.json.

Right-Click and 3-Dot Menus on Chat Rows

The chat row had no in-place way to rename, delete, or do anything besides switch into it. Two paths now work:

  • Right-click anywhere on the row → Pin / Rename / Delete contextMenu.
  • Hover the row → the time on the right swaps to a 3-dot ellipsis button; click for the same menu.

The contextMenu attaches at the row's outer wrapping rather than inside ThreadRow.body. When it lived inside the row, the SwiftUI List cell's hit-test wrapping shadowed it on macOS and right-click silently did nothing. Same for the file browser — the existing right-click menu picked up Pin / Rename / Delete in a consistent order, and the previous swipe-to-delete on macOS (which actually never worked since macOS doesn't render swipeActions) is now iOS-only.

Rename is an alert with a TextField, hoisted to the sidebar level so the alert doesn't dismiss when the row re-renders mid-edit. Delete confirms before destroying.

Command Palette Subtitles Show the Last Message

⌘K used to show "Chat - 3h" / "Skill - 5h" under the chat title — type and age were already in the icon and sidebar list, so the subtitle wasn't pulling its weight. The subtitle now shows the last user-or-agent message from that session (truncated to one line). Reading the palette tells you *what each chat was about* before you switch into it.

Reads come from a CommandSearchSnippetCache keyed by session id + lastEventAt, bounded to the 30 most-recent threads on cold open so power users with many sessions don't pay 100+ JSON decodes synchronously. Result is cached per session, so reopening the palette costs nothing for chats that haven't changed.

The "File - " prefix on file rows is gone (the doc icon already said it's a file). Subtitle font moved to SidebarTypography.metaFont (12pt) so it matches the chat-row time and baseline-aligns with the title.

Sidebar Typography Matches Across Surfaces

Chat-row time labels ("1h", "now") and the ⌘K / ⌘N keyboard-shortcut hints next to Search and New Chat now share a single SidebarTypography.metaFont — 12pt on macOS, 14pt on iOS. The HStacks holding row title + meta align by .firstTextBaseline so the bigger row label and the smaller meta sit on the same baseline instead of drifting on vertical center.

Command Palette No Longer Auto-Scrolls Under Your Cursor

Hovering a row near the bottom of the palette result list used to re-center the list, which slid a different row under the cursor, which became the new selection, which re-centered again — a feedback loop that flickered the list as soon as you moved your mouse. Selection changes from hover no longer trigger the scroll-to-center; only keyboard arrows and query changes do.

File Browser Stops Jittering on Hover

The file browser used .listStyle(.sidebar), which runs its own row-layout pass on every hover state change. That pass fights with the custom macSidebarRowChrome and nudges rows by 1-2px as the cursor moves between them — the long-standing "files push view has jitter" feedback. Switched to .listStyle(.plain) with the same plainSidebarRow() chrome the home sidebar already uses successfully (List for native trackpad swipes, custom chrome for the look, no double layout). Rows now stay rock-steady.

Beeper Tool Descriptions Tightened

The agent kept hitting "I can only search 20 messages, that's a hard limit" when answering "what did we say in #core-ux?". The tool's limit parameter has no hard cap — the model was reading "default 20" as a ceiling and giving up. The descriptions on beeper_search_chats, beeper_search_messages, and beeper_get_chat_messages now spell out that defaults can be exceeded with no hard cap, and steer the model to call beeper_search_chats first when you name a channel, group, or person — then page the chat with beeper_get_chat_messages instead of keyword-searching the firehose.

Pinning Tests

Seven new pinning tests cover chat and file pinning, rename behavior, delete behavior, sidebar filtering, and pinned-file persistence.

Subscription Management Polish

Patina 0.5.7 is a polish + correctness release. The Manage subscription window stops feeling stuck, iOS chat checkboxes finally strike through and sync to Mac in seconds, the chat surface gets its top + bottom gradient fades back, and a few rough edges around connections that quietly fail get a lot less rough.

If you're on 0.5.6, install 0.5.7 directly. Existing chats, files, connections, subscription state, and your chat checkbox toggles all carry over.

Manage Subscription Window No Longer Stalls

The Settings → Subscription → Manage flow had three independent problems that compounded into a window that felt broken. Click Manage and the button would stay disabled for tens of seconds; eventually open into a window with a spinner that wouldn't clear; clicking a plan card would hang for the full 60-second URLSession default before the Stripe checkout URL came back. Three targeted fixes:

  • Auth and subscription roundtrips now have explicit timeouts (8s for status reads, 20s for checkout URLs). A slow worker can no longer pin the UI for the URLSession default.
  • SubscriptionService.refresh(silent:) skips the loading spinner for passive re-validations, so opening the Manage window no longer gates the plan cards behind a roundtrip you didn't ask for.
  • The Manage button itself stops gating on isLoading — clicking it just opens a window, and the window owns its own refresh state.

Plus a separate dual-window bug: clicking "Patina Subscription" used to flip the main app window into the paywall while the Manage window was still open, putting two pickers on screen at once. The provider only flips after Stripe actually entitles you now, so this can't happen mid-checkout.

iOS Chat Checkboxes Strike Through

Tapping a checkbox in chat on iPhone toggled the box but never struck through the body text. Three layers all broke at once:

  • MessageBubble: Equatable was short-circuiting the bubble's body re-evaluation when only the chat task state changed, so SwiftUI never re-rendered the row.
  • The AttributedString -> UITextView bridge on iOS was silently dropping strikethroughStyle, so even after a forced re-render the strike still didn't land.
  • The Files page task body had a similar issue but on a different render path (MarkdownUI.markdownTextStyle(\.text) doesn't re-apply when only the captured checked value flips).

Fixed in three commits: MessageBubble.== now threads a chatTaskStateVersion from ChatTaskStateService, SelectableText takes an explicit strikethrough: Bool flag and applies the attribute at the NSAttributedString level, and the Files page renders task bodies through the same AttributedString-based path the Home dashboard already uses successfully.

Chat Checkbox State Syncs Across Devices in Seconds

Chat task overrides used to persist to local UserDefaults only — checking off a task on iPhone never showed up on the Mac. They now sync through NSUbiquitousKeyValueStore, which is push-driven via APNS and propagates within seconds even when both apps aren't foregrounded together. The previous CloudDocuments file path stays as a backstop in case the KV store ever fails to sync (size cap, account disabled), and mergeRemoteEntries resolves conflicts last-write-wins by toggledAt.

The release script gained a post-seal entitlement guard, mirroring the application-identifier guard the 0.4.0 keychain incident drove us to add. If a future re-sign drops the ubiquity-kvstore-identifier, the release fails before notarization instead of silently shipping a build where cross-device sync is broken.

iOS Chat Top + Bottom Gradient Fades Restored

A 0.5.5 commit set .toolbarBackground(.visible) unconditionally so the iOS home dashboard's header would match the off-white surface. Side effect: every iOS pane lost the system translucent navbar, so chat content butted against an opaque seam at the top with no fade. The bottom seam was always missing — the macOS chat had a clear-to-opaque gradient mask backdrop behind the input pills, but the iOS branch had no equivalent.

Both fixed: .visible only fires for .home mode now, chat + files fall back to .automatic and get the system iOS edge effect; the iOS chat overlay mirrors the macOS gradient mask + theme.background fill so messages feather under the input area instead of stamping a hard rectangle.

MCP Discovery Storm Damping

When a connection's OAuth token expires (e.g. Notion), repeated agent setup calls could fire parallel discoverTools roundtrips, each retrying the failing token refresh ~11 times in parallel. Repeated agent rotations within a chat turn produced 10+ concurrent token_refresh_failed events per server and pinned the main actor on JSON-RPC parsing — which surfaced as dropped frames in chat.

Two cheap defenses in MCPHTTPClient:

  • In-flight Task dedup keyed by serverId. Concurrent callers for the same server collapse onto one roundtrip.
  • 60s failure cache. A known-failing server short-circuits without a network call. Cleared on the next successful discovery, so reconnecting goes through immediately.

This doesn't fix the underlying broken connection — you still need to disconnect and reconnect the failing connector — but the UI no longer freezes while you decide.

iOS Files Page Task Body Font Match

The iOS Files page rendered task body text at the container's 15.5pt while surrounding Markdown chunks rendered at MarkdownUI's .body (17pt). Made the task lines visibly smaller than the prose around them. styledTaskBody now pins .font(.body) to match.

Behavior Pinned With Tests

Eight new source-grep tests across InboxWatcherSpawnsThreadTests and a new ChatTaskCheckboxBehaviorTests cover everything that broke this cycle: the MessageBubble Equatable thread, the iOS SelectableText.strikethrough flag, ChatTaskStateService's dual-write to KV store + iCloud file, the NSUbiquitousKeyValueStore.didChangeExternallyNotification subscription, and the entitlement on both platforms. Cheap, fast, fails loud on regression — same shape as the existing pinning tests.

A Dollar-Based Daily Cap

Patina 0.5.6 is a workflow + cost release. The Patina Subscription daily cap is now a real dollar ceiling, background notifications are easier to notice, and the Home dashboard reflects local file edits the moment you make them.

If you skipped 0.5.5, install 0.5.6 directly. Existing chats, files, connections, and subscription state carry over without any manual steps.

Patina Subscription Daily Cap is a Dollar Ceiling

The subscription's daily fair-use cap was a flat 1.2M-token limit, which over- or under-shot real spend depending on cache hit rate and input/output mix — Daniel hit the cap at roughly $0.41 of actual usage. The worker now caps subscriptions on a $1.00/day cost ceiling computed from real per-model pricing (COST_ESTIMATE_MODEL_PRICING), summed across the paid tier's spend models. Same product, more honest accounting.

The worker also caches the agent's system prompt and tool list per (user, model, hash) on Gemini's cachedContents API and rewrites generateContent calls to reference it. Cached reads are 4× cheaper than fresh tokens ($0.075/M vs $0.30/M), so warm-cache turns spend roughly a quarter of what they used to on the static portion of every prompt.

The Settings usage bar continues to render dollars as a token-equivalent so shipped clients show the correct progress without an app update.

Dashboard Reflects File Edits Instantly

When you check off an item in open-loops.md from the file viewer, the Home dashboard's "On Your Plate" widget used to wait roughly 250ms before catching up — and bursts of writes could stretch that further because each write reset the debounce timer. The notification path is now leading-edge: the first write in a quiet period fires on the next runloop tick, and follow-up writes within a 250ms window collapse into a single trailing flush so agent tool-call cascades still coalesce. Single user actions feel instant; bursts still don't thrash the file browser.

Cross-Thread Agent Notifications on Mac

When a skill delivery (morning briefing, digest, sync) lands in a thread other than the one you're viewing, macOS now surfaces a local notification banner. Tap to route into the thread that received the message. Previously the unread dot in the threads list was the only signal, which was easy to miss while focused on a different chat.

Lighter System Prompt

Roughly 700 more tokens cut from every chat turn (stacks on the 2,700 from 0.5.5). Universal On-Your-Plate verification rules and the GFM task-list rendering rule hoisted into the open-loops block so morning, digest, and weekly skills can stop restating them. Test-asserted strings preserved verbatim; morning briefing implicitly gains the digest's verification rigor.

Smaller Cleanups

  • Command palette: dropped the divider above the results list and put each result's subtitle inline with the title (truncates first instead of wrapping the row).
  • Calendar event title parser now strips every [tag:...] annotation the tool handler emits ([with:, [cal:, [video:, ...) instead of only [with:. Trailing (location) stripping is unchanged.

Slack Connections Work

Patina 0.5.5 is a polish release. Slack connections work again, the Home dashboard reads cleaner, the iPhone navigation matches platform conventions, and the agent's per-turn context is roughly 2,700 tokens lighter without losing behavior.

If you skipped 0.5.4, install 0.5.5 directly. Existing chats, files, connections, and subscription state carry over without any manual steps.

Slack's MCP endpoint advertises OAuth metadata but rejects automatic client registration, so every "Connect Slack" attempt was failing with "The server didn't accept an automatic OAuth client registration." Patina now ships a Patina-owned Slack OAuth app and uses it directly instead of trying to auto-register per user. Existing Slack-curious users can hit Connect again and the flow goes straight through.

The same plumbing is in place for Notion and Linear — they have the same DCR-rejection problem — but their app registrations land in a follow-up release.

Home Dashboard Polish

  • Light-gray page surface with white widget cards and a soft drop shadow under each card and prompt pill, so the cards read as elevated chrome instead of flat strips.
  • Top header chrome and prompt-bar chrome now match the page surface, so there's no contrast seam between the toolbar and the dashboard.
  • The "On Your Plate" checkbox no longer double-toggles. A wrapping gesture was firing alongside the button's own tap, which made the checkmark spring back as soon as you clicked it. Same fix applies to checkboxes inside chat task lists.
  • Drop shadows on prompt pills no longer get clipped at the bottom of the horizontal scroll dock.

iPhone Navigation Matches Platform Conventions

  • Tapping a file from the Home dashboard (e.g. "On Your Plate" → file detail) now pushes the file on top of the current pane with a real back chevron in the top-left, instead of switching to the Files tab and replacing the chevron with the sidebar toggle. Back returns you to wherever you came from.
  • The "Couldn't reach your Mac" error message now reads as itself instead of as "Session expired. Please sign in again." There's no iPhone sign-in to retry — the underlying issue is a stale Mac-bridge auth token, and the new message tells you to quit and reopen Patina on Mac.
  • The auth-token cache is dropped on the next attempt so a freshly-rotated token from iCloud Keychain can take effect without restarting the iPhone app.

Cross-Device Race Fix

A second iPhone code path was still dispatching tool calls in parallel with Mac when both apps were open. With one user prompt, Mac would run the tool against the local Patina folder and iPhone would run the same tool against the iCloud copy that hadn't fully materialized, so the agent saw contradictory results and looped apologizing through six or seven self-corrections. iPhone's silent-push handler no longer dispatches tools — Mac is the sole tool dispatcher in every code path now, matching the foreground polling loop's existing behavior.

Lighter System Prompt

Cut roughly 2,700 tokens off every turn's static system prompt with no behavior loss:

  • Starter-pill authoring instructions moved out of the always-on base prompt into the morning-briefing and digest skills (the only two flows that actually refresh .patina/starter-pills.md).
  • Stale "NEVER use Bash/Edit/MultiEdit" tool warnings collapsed — those tools aren't even registered. Replaced with a one-line summary of the load-bearing rules.
  • The ## Deferred tool schemas Claude-Code-specific footgun explanation is now gated on the Claude provider. Gemini and OpenAI no longer pay 200 tokens per turn for an explainer that doesn't apply to them.
  • Connection blocks (Google Calendar, Gmail, Contacts, Mercury, Figma, Slackdone, Beeper, iMessage, Granola, YouTube, X, Blog, Weather, Mercury) collapsed from 100–300-token paragraphs into one-line pointers. Tool argument formats live in the JSON schemas the model already receives, so the long form was duplication.
  • On Your Plate rules de-duplicated into one canonical block. Skill bodies (morning, digest, weekly, full sync) reference it instead of repeating the structure rules.

Subscription Settings Are Honest

  • Lifetime License accounts no longer see "Requests today" and "Tokens today" usage bars in Settings. Those bars track the Patina Subscription daily fair-use cap, which doesn't apply when you're running on your own Claude or OpenAI key.

Smaller Cleanups

  • The "App Background Activity" notification stops firing on every launch. The launchd plist install is idempotent now — if the on-disk plist already matches what we'd write, we skip the bootstrap and skip the notification.
  • Renamed LifeOSSystemPrompt to PatinaSystemPrompt and the bundled LifeOSBasePrompt.md resource to PatinaBasePrompt.md. Life OS was the app's previous name. Internal-only — no behavior change.
  • Removed an unused marketing page (site/product.html) that wasn't linked from the rest of the site.

iPhone To Mac Chat Sync

Patina 0.5.4 is the cross-device release. Chats started on iPhone now continue on Mac and vice versa, and the iPhone behaves as a true window into the Mac instead of running its own parallel agent. Along the way, the subscription tier is now cleanly Gemini-only, daily-cap behavior is honest, and the Mac log noise from earlier builds is gone.

If you skipped 0.5.3, install 0.5.4 directly. Existing chats, files, and connected accounts carry over without any manual steps.

iPhone ↔ Mac Chat Sync

  • Chat history and active sessions now live in your iCloud Patina folder. A conversation you start on iPhone is openable on Mac, and replies you send from Mac show up on iPhone immediately, without restarting either app.
  • The first time Patina sees a session that was previously stored device-local, it migrates it to iCloud transparently. Older .bak recovery copies stay device-local so a crash on one device can't multiply iCloud usage.
  • A daily background sweep moves any session untouched for 90 days back to local archive storage so iCloud usage stays bounded as your chat history grows.
  • Legacy chats from the old Anthropic Managed Agents path stay browsable in read-only mode through a small backend window, so older threads aren't lost.

iPhone Is Now A Window Into The Mac

  • When both apps are open at the same chat, Mac is the single source of truth. iPhone displays events from iCloud and forwards every typed message to the Mac over Tailscale. Mac runs the agent, dispatches tool calls, and writes results.
  • This fixes a class of bug where a single prompt would produce multiple overlapping responses when both devices were polling the same session and racing to dispatch the same tool calls.
  • Every provider is routed this way now (Gemini, Claude Subscription, Lifetime License Claude, OpenAI BYOK). When Mac is unreachable over Tailscale, iPhone surfaces the existing "Couldn't reach your Mac" error — same UX Claude has always had on iPhone, now consistent across providers.

Subscription Tier Is Cleanly Gemini-Only

  • The Patina Subscription path now runs exclusively on Gemini 2.5 Flash and 2.5 Pro through Patina's own backend. The legacy Anthropic Managed Agents code path is fully removed — including the duplicate provider entry that could quietly route some users through Anthropic instead of Gemini.
  • The Lifetime License tier still runs Claude through the local CLI on Mac, plus BYOK OpenAI / OpenRouter on the OpenAI runtime.
  • The Subscription daily fair-use ceiling is calibrated for the realistic Patina mix (heavy cached input, light output) instead of an all-output worst case. Most days fall well under the cap.

Daily Cap Surfaces Honestly

  • When you do hit the daily ceiling, Patina now shows a terminal "Daily limit reached" message instead of looping on a misleading "Catching my breath, retrying in Xs" prompt. Earlier builds would auto-retry until the session token expired, which generated thousands of pointless backend requests for no benefit.
  • Transient 429s from the model provider (genuinely retryable) still get the soft retry path.

Gemini Reliability Polish

  • Empty-STOP responses from Gemini Flash (a documented Flash quirk that occasionally returns a candidate with no text on a valid prompt) now silently retry once before surfacing an error. Most users won't see them at all.
  • Terminal cases — safety blocks, hit token ceilings, malformed tool calls — still surface immediately on the first response, since those won't change on retry.
  • Title summarization for chats stays on the existing title pipeline; nothing changed visually.

Quieter Logs And Smaller Plist

  • Removed the per-call [Patina] ubiquity container: log line that fired ~50 times per launch on Mac. The path never changed; logging it added noise.
  • Fixed the Exception while (null): A property was not requested when contact was fetched warning that fired on every contacts search by adding the missing CNContactIdentifierKey to the keys array.
  • A v3 cleanup pass on macOS clears legacy Gemini blobs out of UserDefaults so the 4 MB plist ceiling warning stops appearing on launch. Active sessions migrate to iCloud lazily; orphan blobs are dropped on first launch after upgrading.

Smaller Polish

  • The horizontal suggestion pill scroller on the iPhone Home dashboard now fades the edges with a gradient mask when content overflows, matching the Mac.
  • The On Your Plate items in the weekly skill and morning briefing now render as Markdown task list checkboxes again so you can tick them off inline.
  • Pressing Cmd-N to start a new chat now focuses the input field automatically.

Home Dashboard

Patina 0.5.3 is the home release. Opening Patina now lands on a real dashboard with weather, today's plate, and a prompt bar instead of a blank chat. The release also introduces a single canonical "On Your Plate" file behind the scenes, a faster default model on the Patina Subscription, a Cmd-K command search palette across Mac and iPhone, and a noticeably tidier sidebar.

If you skipped 0.5.2, install 0.5.3 directly. Existing chats, files, and connected accounts carry over without any manual steps. The default model on the Patina Subscription has changed; see "Compatibility and upgrade notes" below.

  • A new Home view replaces the blank app launch state on Mac and iPhone. It shows weather, On Your Plate items, and an "Ask Patina anything" prompt bar with contextual suggestion pills.
  • On weekdays Home shows work items first; on weekends it shows personal items. Items come from the unified On Your Plate checklist, so checking something off in chat updates Home immediately.
  • The Mac prompt bar now uses the same multi-line input as the chat composer, capped at four lines so longer prompts grow in place instead of clipping. The horizontal pill strip top-aligns so growing input doesn't shove neighboring suggestions around.
  • Weather widget headline is larger and easier to read at a glance. Wind was removed from the widget to keep it focused on temperature and condition.

On Your Plate Becomes Canonical

  • Reminders, commitments, open loops, and local task fallbacks now all live in one file: open-loops.md. Active items are grouped under ### Work and ### Personal, with a ## Resolved section for items the agent has checked off.
  • Morning briefings and end-of-day digests now read from and write to the same file. Resolved items get checked silently rather than printed as a verification log.
  • Connected task managers like Notion, Linear, and Slackdone remain external upstream sources; the canonical file is for things Patina itself tracks for you.
  • New installs get the file on first launch. Existing installs gain it the first time the morning briefing or digest skill runs.

Faster Default Model On The Patina Subscription

  • The Patina Subscription tier now defaults to Gemini 2.5 Flash for chat, with Gemini 2.5 Pro available as a higher-quality option. Both run through a new dedicated Gemini proxy in the Patina backend, with the same authentication and daily fair-use limits as before.
  • Anthropic models (Opus 4.6, Sonnet 4.6, Haiku 4.5) moved into a separate "Claude Premium" provider option for users who specifically want Claude on the subscription tier.
  • The lifetime license tier still includes BYOK access to Claude, OpenAI, and now OpenRouter routes.
  • Title summarization for chats also routes through Gemini, so threads in the sidebar pick up descriptive titles faster.

OpenRouter And DeepSeek (Lifetime License)

  • Lifetime license users can now point the OpenAI-compatible route at OpenRouter, with key fields for OpenRouter and DeepSeek under the OpenAI runtime in Settings. Pasting a key starting with sk-or- into the API key field auto-detects OpenRouter and switches the route on.
  • Sample OpenRouter models surfaced in the picker include Gemini 2.5 Flash, DeepSeek V3, and Claude Sonnet 4.6 via OpenRouter.
  • Tool calls now continue correctly when routed through OpenRouter, so multi-turn skill flows work the same way they do on the native providers.

Cmd-K Command Search Palette

  • A new search palette opens with Cmd-K on both Mac and iPhone. It searches across quick actions (Home, New Chat), recent chat threads, and Markdown files in your Patina folder, with up/down arrow navigation and Return to open the highlighted result.
  • The palette uses term-prefix scoring, so typing the start of a chat title or file name surfaces it at the top.
  • Mac also gains a "Search" command in the menu bar so the shortcut shows up alongside New Chat.

Sidebar Redesign

  • Search, Chat, and Files now live as dedicated rows above the chats list on both Mac and iPhone, with Cmd-K and Cmd-N shortcut hints visible inline.
  • The Files section is no longer interleaved with chats. Files now lives as its own destination reachable from the sidebar row.
  • Hitting "New Chat" no longer adds an empty draft row to the sidebar. The "Chat" sidebar row stays selected while the draft is in flight, and a real row only appears the first time you actually send a message.
  • The chats section now expands inline with a "View more" affordance instead of jumping to the standalone chat list.
  • Removed the + accessory from the chats section header; the dedicated Chat row replaces it.

Reliability And Subscription Polish

  • Stripe checkout now reports back to Patina with a session-confirmation endpoint, so the welcome step picks up your access faster after returning from checkout instead of polling for up to 90 seconds.
  • When your subscription tier changes, Patina now repairs your selected provider and model automatically. A downgrade from lifetime to subscription (or sign-out) no longer strands the app on a model you can no longer run.
  • Backend usage logging now records per-model token counts and exposes a per-user analytics dashboard at /admin/analytics-user-dashboard for diagnosing usage questions.

User-Facing Voice

  • Skill output now refers to your own actions in second person ("you" / "your") in digests, morning briefings, and notes. The agent recognizes when the actor in a thread is you (via your profile, sent messages, and chat history) instead of writing your name as a third party.

iPhone Marketing Page

  • Added a dedicated iPhone product page on patina.md/iphone covering the iOS companion experience.

Push Notifications And iOS Reliability

Patina 0.5.2 focuses on making the iOS companion app more reliable on first open, tightening push notification delivery, and smoothing the shared chat and files reading experience. It also gives the onboarding and edge-case screens a safer preview path so Gabe can review design states without exposing those controls to everyone.

This release is especially worth installing if you use Patina across Mac and iPhone. The Mac app now has better diagnostics when waking the phone, while the iOS app is less likely to lose an APNs token during the first-run and sign-in timing window.

  • Fixed a first-install race where iOS could receive an APNs token before the auth JWT existed, then skip backend registration forever. Pending device tokens are now retried once auth is available.
  • Registered push tokens now keep track of their APNs environment, so development tokens are sent through the development gateway and release/TestFlight tokens are sent through production.
  • Notification taps now route through UNUserNotificationCenterDelegate, including cold-launch notification payloads, so tapping a banner can open the intended pushed thread instead of only opening the app.
  • APNs send failures now surface more useful error information instead of disappearing behind a simple success count, making BadDeviceToken, DeviceTokenNotForTopic, InvalidProviderToken, and payload errors much easier to diagnose.
  • Silent push refresh no longer depends on an already-instantiated chat view. Background launches can refresh through the app/session layer without requiring the UI to already be alive.
  • The iOS launch flow is clearer around server hiccups and authentication errors, including simpler retry treatment and less noisy device-registration copy.

Onboarding And Settings

  • The first-launch screen now uses the real Patina app icon with an iOS-style corner radius, the updated tagline, and the custom thinking animation during loading states.
  • iCloud connection loading also uses the custom thinking animation for a more consistent onboarding feel.
  • Added gated design previews in iOS settings for first launch, auth errors, iCloud connection, and other edge-case states. These previews are only available to gabe@valdivia.works.
  • The iOS sidebar keeps Settings pinned to the bottom while preserving the Show More affordance as a route into the chats/files menu.
  • Removed unnecessary subscription-plan choice from the iOS onboarding path so billing stays managed from macOS.

Chat, Files, And Visual Polish

  • Bold markdown in chats and files now renders as semibold, which keeps emphasis visible without making generated text feel too heavy.
  • The chat scrollbar now sits above the bottom gradient treatment instead of being visually buried by it.
  • Suggested prompt pills now use regular-weight text at the same size as the message input, with the larger pill spacing preserved.
  • Literal bullet glyphs from generated responses, such as • item, now parse as real unordered lists so wrapped lines use the same hanging indent as Markdown bullets.
  • Tool-call rows, thinking states, and related chat surfaces received small spacing and typography refinements.
  • The Mac feedback input has more comfortable inner padding, and the desktop top header no longer draws an extra bottom border.

Mac Interface Polish

Patina 0.5.1 is a polish and hardening release after the larger 0.5.0 feedback update. It smooths the Mac detail header across chats and files, tightens daily-briefing behavior around contacts and open loops, and removes a few rough edges from sign-in, diagnostics, and operational tooling.

This release also includes a small security cleanup for Beeper authentication state. Existing users do not need to do anything manually, but future endpoint sharing keeps the token out of the synced endpoint file.

Mac Interface

  • Replaced the translucent gradient at the top of the Mac detail pane with a shared opaque 44 pt header for both Chats and Files.
  • Added a real bottom separator to the detail header and gave chat and file content matching top padding below it.
  • Kept the back and forward buttons in the same shared component and position across chat and file views.
  • Moved the file Edit/Done action into the shared file header as a plain text action, without a capsule container.
  • Improved the recent-files section in the sidebar so the Files header and first recent file read as one cleaner group.
  • Refined the Mac subscription sign-in panel so six-digit code entry is clearer and the code input can be reused across onboarding and paywall sign-in.

Daily Briefings And Contacts

  • Contacts search now handles names, email addresses, and phone-number-only queries more reliably, including common phone-number variants.
  • Patina's briefing and digest instructions now ask the agent to resolve phone-number-only conversations through Contacts before labeling someone unknown.
  • Open-loop verification is stricter about timestamps, outbound replies, archived email, and later participant replies before carrying an item forward.
  • Morning and digest headings now prefer smaller section headings for ordinary sections so generated markdown reads more cleanly in the app.

Chat, Skills, And Diagnostics

  • Chat input focus handling on macOS is steadier when another text view already owns focus.
  • Markdown and task-list rendering received small fixes backed by regression tests.
  • Skill onboarding and in-app run behavior have additional coverage so bundled skill updates remain compatible with existing user-owned files.
  • Feedback report notification emails now go to the team after a report is stored, with tighter HTML typography and tests around the notification payload.
  • Local logging now formats the sanitized metadata that is actually stored with a log entry, keeping console output aligned with diagnostic capture.
  • The worker now lets the shared CORS wrapper own CORS headers on Anthropic proxy responses, avoiding duplicated response header handling.

Privacy And Operations

  • Beeper access tokens are now read from and migrated into Keychain instead of staying in UserDefaults.
  • The shared Beeper endpoint file no longer publishes the access token, including from the Mac to iCloud-backed state.
  • Disconnecting Beeper clears the stored token from Keychain as well as legacy defaults.
  • Granola access tokens now migrate from the old shared file into Keychain, and the legacy shared token file is removed after migration.
  • The Mac-to-iOS bridge shared secret continues moving through iCloud Keychain rather than the shared bridge JSON file, with comments updated around that compatibility path.
  • iOS APNs device tokens now move through synced Keychain and the backend token registry instead of a plaintext iCloud Drive JSON file.
  • Mac-initiated push wakes now only use an explicit device token if that token is already registered for the user, and stale tokens are pruned from the registry after APNs rejects them.
  • MCP OAuth access and refresh tokens are now persisted in Keychain and stripped from UserDefaults and mirrored shared connection state.
  • Google account access tokens are stripped from per-folder and shared connection snapshots so synced state keeps account identity without carrying bearer tokens.
  • Removing or reconnecting MCP servers clears the corresponding stored secrets so stale tokens do not survive a disconnect.
  • Added regression coverage that confirms Google and MCP OAuth tokens are stripped before persistence.
  • The dashboard script now requires ADMIN_KEY from the environment instead of carrying a hard-coded admin key.
  • The test setup helper now requires PATINA_TEST_TOKEN from the environment instead of carrying a checked-in test JWT.
  • The public site and pricing page received copy and layout cleanup carried forward from the current app positioning.

The Home Release

Patina 0.5.0 is a larger reliability and feedback release. It gives users a direct way to report problems from inside a chat, gives the team the redacted context needed to debug those reports, and makes the Mac tool bridge more resilient before Claude Code tries to call connection-backed tools.

This release also smooths out a few very visible surfaces: selecting text in chat bubbles is steadier, the analytics dashboard is easier to operate, and the public site does a better job explaining the product and the lifetime license path.

  • Added an in-app feedback flow on Mac and iOS. Users can send a comment from the chat surface and optionally include the current transcript and recent logs.
  • Feedback submissions now return a report ID in the app so a user-visible issue can be tied back to the stored diagnostic bundle.
  • Added a new authenticated /v1/feedback backend endpoint that rate-limits reports, stores them in KV with a retention window, and redacts common sensitive patterns before persistence.
  • Added an admin feedback view endpoint so recent reports can be reviewed without digging through raw KV keys.
  • Improved chat text selection, especially in user bubbles, by reducing competing selection layers and moving macOS user-bubble text onto the native selectable text path.

Chat And Feedback

  • Added a feedback button to the Mac chat header and the iOS chat toolbar.
  • The feedback sheet can include session state, selected model, current error, connection status, recent message summaries, stored session events, and recent app logs when the user chooses to include them.
  • Diagnostic capture redacts emails, bearer tokens, JWTs, API-key-like strings, Social Security number patterns, and card-like number patterns before upload.
  • Recent app logs can now be read from the logging service for diagnostics without exposing the whole log file.
  • User bubble selection now keeps a clearer highlight and avoids the blue-to-gray selection handoff that happened when SwiftUI and AppKit selection systems fought over focus.

Tool Bridge Reliability

  • Patina now ensures the local Mac tool bridge is started before installing Claude Code's MCP bridge for connection-backed tools.
  • Added a local bridge health check and wait loop so Claude Code turns have a better chance of reaching Gmail, Google Calendar, and other Patina-backed tools on the first attempt.
  • The Mac tool bridge can fall back to a dynamic local port if the default port is already occupied, while still publishing the expected Tailscale Serve entry point for iPhone-to-Mac calls.
  • The Python MCP bridge now retries transient local connection failures and returns a clearer "bridge offline" diagnostic instead of implying that Gmail or another source is disconnected.
  • Added tests to keep the MCP bridge and Mac tool bridge port contract aligned.

Analytics And Operations

  • Refined the private analytics dashboard with icon-only refresh and settings controls, cleaner responsive toolbar behavior, and more stable chart bar sizing.
  • Added a backend test for feedback report storage and redaction so sensitive diagnostic content does not leak into stored reports.
  • Kept feedback payloads capped by body size, nesting depth, array length, object key count, and per-string length to protect the worker and KV store.

Website And Pricing

  • Updated the landing page with a cleaner "day with Patina" carousel, including explicit pause/resume controls.
  • Improved the people section physics so floating avatars avoid covering the main copy while the section is in view.
  • Added a subtle interactive treatment to the final call-to-action card on pointer devices while respecting reduced-motion preferences.
  • Reworked pricing copy around choosing a plan, and changed lifetime license interest into an email capture flow instead of sending users directly to the installer.

Chat Launch Reliability

Patina 0.4.27 makes the first-run experience feel much more like a product you can settle into. New installs now get a built-in guidebook inside the Patina folder, onboarding is more responsive and visual on different Mac window sizes, and the app does a better job explaining what it needs when connecting local sources like Messages and Notes.

This release also tightens a few operational pieces around analytics, subscription state, and installer packaging so the shipped app is easier to understand, measure, and trust.

  • Added a new guides/ folder during onboarding with readable product guides for getting started, chat and files, skills, automations, connections and sync, the Patina folder, and privacy controls.
  • Seeded those guide files into the recent-files list so new users land on useful documentation instead of only seeing empty starter folders.
  • Reworked the onboarding window so the intro step adapts better on narrower desktop windows while keeping the visual column on wide layouts.
  • Added richer people artwork to the onboarding and landing-page experience.
  • Refined the Messages and Notes setup steps so Full Disk Access guidance is clearer and better aligned with the macOS grant flow.
  • Updated starter prompts to nudge users toward practical follow-up and meeting-prep workflows.
  • Improved prompt suggestion labels and empty-chat presentation so suggestions scan more like available actions.

Fixes And Reliability

  • Added a Full Disk Access primer that touches stable protected locations before opening System Settings, helping macOS register Patina in the permission list before the user grants access.
  • Changed the Full Disk Access copy to point users toward the macOS quit-and-reopen prompt instead of leaving them to guess why a new permission grant has not taken effect yet.
  • Improved hidden skill-run handling so onboarding and scheduled skill threads can resolve skill instructions locally and mark runs consistently.
  • Reduced unwanted chat auto-scroll while assistant output is arriving, making it less likely that a long answer pulls the reader away from the part they were inspecting.
  • Hardened file sidebar path handling by computing relative paths from standardized URLs instead of string replacement.
  • Added tests for the Full Disk Access probe order and subscription resolution state.
  • Restored the documented DMG Applications Finder alias in the release script so the drag-to-Applications installer layout keeps the visible Applications target.

Analytics And Operations

  • Refined the private analytics dashboard with range presets, custom ranges, a settings dialog for the admin key, live refresh controls, and a tighter layout.
  • Changed analytics day bucketing and dashboard defaults to use America/New_York so daily reporting matches the team's operating timezone.
  • Kept analytics reporting focused on aggregate product signals and scrubbed properties rather than user content.

Website And Pricing

  • Updated the landing page to match the new onboarding direction and people-forward visual treatment.
  • Added a dedicated pricing page so prospective users can understand the subscription before installing.
  • Tuned the desktop people-frame transform on the marketing site.

Safer Local Files

Patina 0.4.26 adds product analytics so we can understand how the app is being used without collecting the content people trust Patina with. The release adds a Settings toggle, private aggregate reporting, and a live admin dashboard for core usage signals like active users, chats, prompts, prompts per chat, queued prompts, assistant responses, and tool use.

This is intentionally a privacy-forward instrumentation release: users can turn analytics off in Settings, events are scrubbed before they leave the app, and prompt text, file names, emails, and local content are never sent.

  • Added an Analytics section in Settings with a Share usage data toggle.
  • Added aggregate usage instrumentation for app opens, chat creation, prompt sends, queued prompts, assistant responses, and observed tool calls.
  • Added a private analytics ingestion endpoint on the Patina API worker.
  • Added a live custom analytics dashboard with active users, prompt volume, chats, prompts per chat, tool usage, queued prompts, provider mix, platform mix, and daily trends.
  • Added server-side forwarding to PostHog so the same scrubbed events can power richer product analytics without adding another client-side SDK to the app.

Privacy And Data Handling

  • Analytics are on by default and can be turned off in Settings.
  • Events use a device-scoped anonymous analytics identifier instead of prompt or document content.
  • The app does not send prompt text, assistant text, file names, email bodies, message bodies, note contents, or local document contents.
  • The worker validates event names and properties against allowlists before storing or forwarding analytics data.
  • PostHog receives only the scrubbed server-side event payload, not raw app content.

Dashboard And Operations

  • Added a private /admin/analytics endpoint for aggregate JSON reporting.
  • Added a private /admin/analytics-dashboard page for real-time dashboard viewing.
  • The main dashboard chart now shows daily active users, while prompts and chats remain visible in summary cards and the daily table.
  • Metric cards were tightened to keep the dashboard focused on the primary numbers.
  • The worker keeps first-party daily aggregate counters in Cloudflare KV while also forwarding sanitized events to PostHog when configured.

Reliability And Tests

  • Added backend coverage proving analytics batches update daily counters without storing prompt text.
  • Added backend coverage proving PostHog fan-out sends only allowed, scrubbed properties.
  • Kept the analytics upload path batched and non-blocking so telemetry does not slow down chat interactions.
  • Adjusted test-only worker exports so local Wrangler development can run the worker cleanly.

Healthier Background Sync

Patina 0.4.25 makes morning briefings better at catching the small real-world commitments that usually hide in confirmation emails. If you sign up to bring juice and fruit, RSVP to something, reserve a table, book an appointment, or receive a school/activity confirmation, Patina now has a clearer path to notice that commitment and put it in the morning briefing when it matters.

This release is intentionally focused: it turns the SignUpGenius-style reminder use case into shipped briefing behavior without adding a new inbox, database, or workflow for users to manage.

  • Morning briefings now scan Gmail for commitment receipts: signup confirmations, volunteer assignments, reservations, tickets, appointments, deliveries, RSVPs, and school or activity emails.
  • The briefing prompt now explicitly looks for concrete future obligations such as things you promised to bring, attend, pay, submit, pick up, prepare, RSVP to, or remember.
  • Due-today and due-soon commitment receipts now belong in the ## Reminders section alongside unchecked reminder files.
  • The SignUpGenius pattern is covered directly, including the sort of “bring apple juice and fresh fruit on Friday” reminder that prompted this release.

Existing Users

  • Existing bundled /morning skills are patched on reload when they still look like Patina's unmodified morning briefing template and do not already contain the new commitment-receipt scan.
  • User-owned or heavily edited skills are not clobbered. The updater only applies the narrow prompt patch when the existing skill still has the known bundled sections.
  • Conversational morning briefings also learn the new behavior through the dynamic system prompt when Gmail is connected, so asking Patina for a morning briefing and scheduled /morning automations both benefit.

Reliability And Guardrails

  • Commitment receipts must be concrete and dated before they appear in a briefing.
  • Generic confirmations, ads, newsletters, expired items, and emails that do not clearly imply an action for the user should be left out.
  • The extraction step keeps source verification in the model's private reasoning while showing the user a concise reminder instead of noisy email metadata.
  • Existing open-loop cross-checking still applies: Patina continues to check sent mail, archived state, iMessage, and Beeper before flagging email or message items as unresolved.

Tests

  • Added coverage that the seeded morning skill includes the Gmail commitment-receipt scan, SignUpGenius handling, and the concrete-and-dated rule.
  • Added coverage that the dynamic Gmail-connected morning briefing prompt includes the new commitment-receipt instructions.
  • Added a migration-style test that simulates an existing bundled /morning skill and verifies that reload patches in the new receipt behavior without replacing the whole skill.

Onboarding And Bridge Polish

Patina 0.4.24 focuses on making the app feel steadier across devices and clearer for new users. It tightens the Mac-to-iPhone bridge around Tailscale, simplifies the internal client-tool registry, improves local session-event storage reuse, and gives onboarding richer starter people profiles when Messages and Contacts are connected.

This release also ships a refreshed website and legal-page hygiene work so the public download, pricing, privacy, and fair-use language stay aligned with the current product.

  • iPhone-to-Mac requests now prefer an explicit Tailscale bridge endpoint. When Tailscale Serve is available on the Mac, Patina publishes the MagicDNS URL and proxies through Tailscale instead of asking iOS to try a list of LAN candidates.
  • Bridge failures now explain the real recovery path more directly: make sure Patina is running on the Mac and that Tailscale is connected.
  • Onboarding can seed up to ten starter people profiles from recent one-to-one iMessage activity, including a short relationship summary and recent local conversation snippets to make those files useful immediately.
  • The iOS sidebar rows have larger tappable regions, making thread selection feel less fussy.
  • The marketing site has a more complete interactive Mac mockup, updated pricing copy, a clearer people-follow-up section, and no placeholder "Learn more" buttons in the pricing cards.

Reliability And Architecture

  • Client-tool definitions, availability, platform routing, and execution now come from the shared ClientToolRegistry instead of a separate legacy name list.
  • Mac-only tool detection now uses registry metadata, reducing the chance that an iOS tool call is routed incorrectly.
  • Stale bridge payloads are still supported, but iOS now keeps to the preferred endpoint rather than cycling through every published LAN address.
  • Claude Code and OpenAI session-event persistence now share the same file-backed local event log helper, including lazy migration from legacy UserDefaults storage and a one-file backup path for recovery.
  • Short bridge requests fail faster when the Mac or Tailscale is unreachable, while long Claude turns keep their longer request window.

Website, Docs, And Policy

  • The website pricing section now presents the subscription and lifetime options without inactive secondary buttons.
  • The people section now describes Patina as helping you stay present by tracking unanswered replies, neglected threads, and connections that need attention.
  • Terms now include the Patina Subscription fair-use policy for included Claude usage.
  • Privacy and terms mirrors were brought in sync with the canonical website pages.
  • Added a security posture note documenting why the macOS app is intentionally not sandboxed today and what defenses remain in place.
  • Added lightweight audit scripts for tracked source/documentation files and legal-page mirror checks.

Tests

  • Added tests that ensure every registered client tool is recognized by the handler.
  • Added coverage for MCP tool recognition and registry-backed Mac-only routing.
  • Updated bridge endpoint tests to lock in explicit Tailscale URL preference and legacy fallback behavior.
  • Updated system-prompt and bridge tests for the current Mac bridge/tool-routing behavior.

Onboarding Window Hotfix

Patina 0.4.23 is a small onboarding hotfix for new users moving through the setup flow on narrower displays or browser-heavy desktops. It gives the onboarding window more room so the account-connection step can show its guidance, status panels, and Continue action without clipping the content.

  • The onboarding window now opens on a larger fixed canvas, matching the amount of content shown in the later setup steps.
  • The preview window uses the same shared canvas size as the real onboarding flow, so local checks and the shipped app stay aligned.
  • The left-column spacing was tightened slightly to make longer connection status panels feel less cramped while preserving the existing two-column onboarding layout.

Fixes And Reliability

  • Fixed the Notes/connection step clipping that could make the setup content look cut off when a provider status panel was expanded.
  • Reduced the chance that the Continue button or explanatory setup text appears visually squeezed during the final onboarding pages.
  • Verified the macOS debug build after the layout change with xcodebuild -project Patina.xcodeproj -scheme Patina_macOS -configuration Debug -derivedDataPath /private/tmp/patina-derived-data build.

Lifetime License Hotfix

Patina 0.4.22 is a focused hotfix for the Lifetime License onboarding path after checkout. It fixes the case where a user bought the lifetime license, returned to Patina, clicked "Enter API key", and was advanced into the rest of onboarding instead of being shown the API-key entry screen.

  • The "Enter API key" action now opens the API-key entry panel when the user's Lifetime License is active and no provider key is stored yet.
  • If the user already has an Anthropic or OpenAI key saved in Keychain, the button can still continue forward through onboarding.
  • The runtime selection still defaults to the Lifetime License path, but the visible action now matches what happens on click.

User Impact

  • A user who has just completed Lifetime checkout should return to Patina, click "Enter API key", paste their Anthropic or OpenAI key, and then continue onboarding.
  • The user should no longer be sent into the privacy/storage/connection onboarding steps before they have had a chance to enter the key.

Validation

  • The macOS app was build-checked with xcodebuild -project Patina.xcodeproj -scheme Patina_macOS -configuration Debug -derivedDataPath /private/tmp/patina-derived-data build.

Sign-In Hotfix

Patina 0.4.21 is a focused hotfix for the first-run sign-in flow. It fixes a second onboarding path where a new user could still end up on the plan picker after entering an email, with both plan cards disabled, instead of staying on the code-entry screen.

  • The onboarding plan step now defensively redirects back to the sign-in/code step whenever the user does not have a verified email on their Patina account.
  • The pending sign-in email is now stored in onboarding state, not only in a transient text field. If SwiftUI remounts the sign-in view after the user sends a code, Patina remembers the email and keeps the code-entry screen usable.
  • The pending sign-in email is included in the onboarding persistence snapshot, so a restored onboarding session does not forget which email requested the code.
  • This prevents the disabled "Pick your plan" screen from becoming a dead end for new users who have not finished email verification.

What Users Should See

  • A new user enters their email.
  • Patina sends the six-digit code and remains on the code-entry step.
  • After the code is accepted, Patina can safely move to the plan selection flow because currentUser.email is now verified.
  • If any stale onboarding state tries to reopen the plan step without a verified email, Patina automatically falls back to sign-in instead of showing disabled billing cards.

Validation

  • The macOS app was build-checked with xcodebuild -project Patina.xcodeproj -scheme Patina_macOS -configuration Debug -derivedDataPath /private/tmp/patina-derived-data build.
  • This release is intentionally narrow and does not require a data migration.

Sign-In And Billing

Patina 0.4.20 fixes a first-run sign-in edge case that could send a new user to the billing screen before they had a chance to enter their email code. It also includes a batch of chat polish around attachments, tool activity labels, message rendering, and automatic chat titles.

  • New users now stay on the email-code step until their email is verified. The plan picker no longer appears as a disabled billing screen before sign-in is complete.
  • Checkout now uses the verified email from the authenticated Patina account rather than whatever happens to be typed into the visible email field.
  • The billing gate on macOS now clearly reads as a sign-in screen for unsigned users, then switches to the plan picker only after the code is accepted.
  • The onboarding plan picker uses the same verified-email guard before opening Stripe checkout, which keeps first-run and later billing flows consistent.
  • On iOS, the billing screen now shows the verified account email as read-only once sign-in succeeds instead of leaving an editable field that checkout does not actually use.

Attachments

  • PDF files can now be staged from the iOS composer using the Files picker.
  • Staged PDFs are stored alongside other Patina attachments in the user's Patina folder so they survive cache clears and sync with the rest of the workspace.
  • Chat bubbles now render document attachments as file chips, with filename and type, instead of treating every attachment as an image.
  • PDF attachments are sent to Claude Code as native document blocks when that runtime is used, while image attachments keep their existing image-block behavior.
  • Restored or replayed chat turns now rebuild PDF attachment bubbles correctly from the saved relative paths.

Chat Interface

  • Tool activity labels are more specific. Shell commands now show concise command summaries such as git, rg, or ls instead of the generic working label.
  • Tool-search activity now reads like "Finding available tools", "Loading 2 tools", or "Looking for file tools", which makes agent setup and delegated tool loading easier to follow.
  • Tool group labels now truncate cleanly instead of crowding the activity row.
  • File attachments in user messages can be opened directly from the bubble.
  • Several chat surfaces received small layout fixes so attachment previews, tool calls, and message content stay more legible.

Titles and Reliability

  • Cleanup requests now produce clearer automatic titles such as "Tasks Cleanup", "Duplicate Tasks Cleanup", and "Figma Comments Cleanup".
  • The backend title prompt includes the same cleanup guidance, so generated titles and local fallback titles now agree better.
  • Regression tests cover cleanup title extraction and sanitization to keep those titles from drifting back into vague word salad.
  • Attachment staging now sanitizes suggested filenames before writing files into the Patina folder.
  • The macOS app was build-checked with xcodebuild -project Patina.xcodeproj -scheme Patina_macOS -configuration Debug -derivedDataPath /private/tmp/patina-derived-data build.

Chat Sending Reliability

Patina 0.4.19 focuses on making the chat composer feel less fragile while a response is in flight, and on exposing the newest OpenAI models for people running Patina with their own API key. It also tightens a few rough edges in markdown rendering and gives the onboarded assistant clearer guidance for asking useful setup questions.

Chat and sending

  • You can now compose and queue another message while Patina is still thinking or streaming a response. Instead of blocking the input, Patina keeps the draft send ready and automatically sends it once the current response settles.
  • Queued messages appear above the composer so it is clear what will be sent next.
  • A queued message can be removed before it is sent. If the queued message had staged attachments, Patina cleans those staged files up when the queue item is removed.
  • The send button remains available during streaming when there is text or an attachment ready to send, while the stop button remains available for interrupting the current response.
  • Chat polling now treats an in-progress send request as active work, which avoids brief idle-state flicker while the app is waiting for the provider to accept the next message.

Model selection

  • The Lifetime License runtime picker now separates Claude Code, Anthropic API, and OpenAI API instead of grouping all API-key usage under a generic label.
  • OpenAI is now available as a first-class runtime choice from Settings when using a Lifetime License.
  • The OpenAI model dropdown now defaults to GPT-5.2 and includes GPT-5.2 Pro, GPT-5.2 Chat, GPT-5 Mini, and GPT-5 Nano.
  • Stale OpenAI selections now normalize to GPT-5.2 so new OpenAI API-key users start on the current flagship model instead of the smaller mini model.

Reading and formatting

  • Agent prose rendering now keeps headings, paragraphs, unordered lists, and ordered lists in one selectable text flow where possible. This improves native text gestures such as paragraph selection across normal markdown responses.
  • List spacing was adjusted so bullets and ordered-list numbers align more naturally in the merged selectable text view.
  • Heading spacing in merged markdown was tightened to avoid excess vertical gaps in ordinary assistant responses.

Assistant guidance

  • Patina's base prompt now asks the assistant to include a few lightweight examples when it asks onboarding-style questions about current projects, goals, habits, or important people.
  • The generated Swift system prompt carries the same guidance, with a regression test to make sure the instruction stays present.
  • This should make early setup conversations easier to answer because users get a clearer sense of the useful range: work projects, habits, relationships, lingering errands, and similar real-life context.

More Dependable Sessions

Patina 0.4.18 makes the starter suggestions above the chat input feel more alive and better aligned with the day in front of you. The pills are still meant to stay quiet and ambient, but they now pay direct attention to today's calendar instead of waiting for another workflow to rewrite the starter-pills file first.

  • Starter suggestion pills now check today's Google Calendar events when a fresh chat opens.
  • Upcoming timed events can appear as "Prep me for ..." suggestions, so the next useful action is visible without needing to ask what is on the calendar first.
  • Calendar-aware starter pills refresh again on the existing 15-minute cadence, keeping the row closer to what has changed during the day.
  • The next upcoming calendar event can show immediately, while later meetings stay gated to their useful prep window.
  • Suggestion pills are now aligned with the left edge of the input bar, so the bottom chat area reads as one clean surface.

Suggestion Pill Behavior

This release keeps the existing starter-pill merge behavior, but gives calendar items a first pass. On a fresh chat, Patina now builds the row from calendar-derived prep prompts, time-of-day prompts, personalized starter pills, and general connected-source fallbacks. It still caps the row at five suggestions so the interface stays compact.

Calendar-derived pills use the same plain prompt shape as the rest of the app. For example, a meeting called "Design review with Sarah" can become "Prep me for Design review with Sarah." Long event names are shortened so the pill does not take over the row.

If Google Calendar is not connected, or if there are no upcoming timed events today, Patina simply falls back to the existing starter suggestions. There is no error banner or extra setup prompt in the chat surface.

UI Polish

  • The left edge of the suggestion row now uses the same horizontal inset as the input bar.
  • The left fade on the horizontal pill row now appears only after the row has actually been scrolled. At rest, the first pill is no longer faded at the left edge.
  • The right fade remains visible to hint when more suggestions are available offscreen.
  • The manual refresh icon experiment has been removed. Suggestions update automatically instead of asking the user to manage the list.

Reliability Notes

Calendar refresh work is cancelled when the chat view disappears, so stale background fetches do not update a view that is no longer active. Calendar pills are also recomputed when the calendar fetch completes, so a fresh chat can update as soon as the events arrive.

The release continues to preserve the existing follow-up suggestion behavior after assistant replies. Calendar-aware starter pills apply to the fresh-chat starter state; follow-up pills still come from the assistant's latest response.

Release Pipeline Hardening

Patina 0.4.17 is the corrected follow-up release after 0.4.16. It keeps the same user-facing improvements that were prepared for the previous build, but publishes them under the next version number so Sparkle, the GitHub Release, and the app's internal version history all move forward cleanly.

  • User message bubbles now size like real chat bubbles. Short replies hug their contents, while longer messages wrap at a capped maximum width instead of every outgoing message being forced into the same wide shape.
  • Chat titles are more reliable and concise. New conversations should get a genuine 2-5 word title rather than a truncated copy of the opening user message, including common testing prompts and long pasted text.
  • Interrupted assistant turns are preserved after quit. If the app closes while an agent is thinking, reopening the conversation shows that the in-progress turn was interrupted instead of quietly dropping that state.
  • Running threads are easier to spot from the chat list, so background work is less mysterious when a conversation is still active.
  • Tool-call rows expand with more useful detail and less repeated text. The collapsed state stays compact, while the expanded state better explains what was browsed, searched, read, or grouped.

Chat and UI fixes

  • Added Command-N support for quickly starting a new chat.
  • Themed links now use the selected app accent in chat and markdown surfaces.
  • Empty markdown table cells keep enough height to remain readable.
  • Morning briefing markdown rendering has been tightened up.
  • iOS sidebar navigation for chats and files has been corrected.
  • Automation launch agents are attributed to Patina in system surfaces.
  • Onboarding chrome, spacing, code-entry behavior, and plan-picker layout received a round of polish.

Account and subscription reliability

  • Sign-in and plan selection now fit together more cleanly for Stripe-backed plans.
  • Subscription controls are consolidated into Settings, including restore and cancel flows.
  • Read-only subscription states are enforced more consistently while preserving local background work that does not need a paid cloud-backed model route.
  • Billing auth paths have been hardened and covered with tests.
  • Provider environment mismatches are handled more gracefully.
  • Legacy free-tier entitlement behavior has been removed in favor of the current subscription model.
  • The worker magic-link deployment path has been fixed.

Subscription-Era Polish

Patina 0.4.16 focuses on making the subscription-era app feel calmer and more dependable: clearer onboarding, safer billing behavior, better chat polish, and more honest handling of work that is still running when the app closes.

  • New users now move through a more deliberate sign-in and plan selection flow before starting with Stripe-backed plans. The onboarding window has been cleaned up with tighter spacing, a native-feeling plan picker, better back and close button placement, and clearer copy around email code entry.
  • Subscription controls are consolidated into Settings, with restore and cancel paths available from the plan area. Plan rows stay hidden until the user is signed in, which avoids showing account actions that cannot work yet.
  • Read-only account states are handled more consistently. Patina now enforces read-only chat behavior for expired subscriptions while still allowing local background work that should not require a live paid LLM route.
  • Chat titles are more reliable and shorter. New chats should get a real 2-5 word title instead of a truncated copy of the first message, including common prompts about message bubble testing.
  • User chat bubbles now behave like bubbles again: short messages wrap around their content, while long messages use a capped max width instead of every message being forced to the same size.
  • Tool calls expand with more useful details and less repeated text. The collapsed row remains scannable, while the expanded content better explains what was actually searched, read, or browsed.
  • Running threads are represented in the sidebar, and interrupted turns are preserved after quit. If you ask something, Patina starts thinking, and the app exits before the agent finishes, the reopened conversation now shows that the turn was interrupted instead of silently losing the in-progress assistant message.

Chat and UI fixes

  • Added Command-N support for quickly starting a new chat.
  • Themed links now match the selected app theme, including links rendered inside chat messages.
  • Empty markdown table cells keep a usable row height, which prevents compact tables from collapsing into awkward strips.
  • Morning briefing markdown rendering has been tightened up.
  • iOS sidebar navigation for chats and files has been corrected.
  • Automation launch agents are now attributed to Patina so background work is easier to understand in system surfaces.
  • Several onboarding details were refined, including sign-out behavior, code-entry flow, preview background color, and window chrome alignment.

Billing and account reliability

  • Billing auth paths have been hardened and covered with tests.
  • Provider environment mismatches are handled more gracefully, reducing confusing account state when local and remote configuration disagree.
  • The old legacy free tier entitlement has been removed so plan behavior is driven by the current Stripe subscription model.
  • The worker magic-link deployment path has been fixed.
  • Lifetime license feature copy has been updated to better match the current product surface.