Repository navigation
Prepare the 0.6.0 release - #475
Conversation
File the unreleased Files API changes under 0.6.0, a minor release because they include accepted API breaks, and add the Demo Files screen to it. Update the local-check example in CONTRIBUTING to the new tag. The API breakage allowlist stays as is: its entries are breaks against 0.5.3, which CI compares with until 0.6.0 is tagged. Empty it after that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a read-only smoke case that lists files and filters them by purpose, a case for the OpenAI-only placeholder on other providers, an upload, download and delete round trip, and a failed upload with the placeholder key. Add the Files section to the Misc menu case. The document picker can be driven in the simulator once a file is in the Files app's On My iPhone folder, so the skill now writes one there after installing Demo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The build named an "iPhone 17" simulator, which a machine may not have. Build for the generic iOS Simulator destination instead, which covers every simulator architecture, boot any available iPhone when none is running, and report whichever device the run used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FilesView and the upload sheet both showed FilesStore.error in an alert. When an upload failed, FilesView's alert dismissed the sheet it was presenting, and dismissing the sheet's alert along with it cleared the error, so the sheet closed and no error stayed on screen. FilesView now leaves errors to the sheet while it is presented. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ExyteChat's ChatView applies every change to its messages as a separate table update, in a serial queue that doesn't merge them, and an animated update takes about 465 ms. ChatStore and ResponsesStore throttled their published messages to one snapshot per 100 ms while streaming, about five times faster, so updates queued up and a long reply kept growing on screen long after the stream had finished, with the Responses title already saying it was done. The stores now publish every change right away and know nothing about the chat view. DetailView and ResponsesChatDetailView pass their messages to ExyteChat through ChatMessagesPacer, which passes on at most one change every 500 ms, always the latest messages, and the first one right away. Measured with a web search reply in Responses: without pacing, 39 updates were still queued when the stream completed and the last one landed 15.5 s later; with it, at most one is queued and the last one lands about 0.2 s after the stream completes. Updating without animation would let every change through, but ExyteChat takes the animation mode only from updateTransaction, which returns before the table receives the update; in a test, only 16 of 26 updates came through unanimated. ChatStore no longer keeps a delayed copy of its conversations, so the conversation list binds to them directly and deleting a conversation removes it from the store. ResponsesStore replaces the last conversation turn right away instead of after the delay, when it might no longer be the turn being streamed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every PR description starts with a short list of only the main changes, with less important changes and other details further down for readers who want them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It needs more work before agents should follow it. This reverts commit f54eaa1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
3 open issues
What changed in this PR
Release prep for 0.6.0 plus Demo UX fixes (Files error handling and chat streaming performance) and updates to the demo release-check skill/docs.
Changes:
- Add
ChatMessagesPacerand wire it into Demo chat views to throttle ExyteChat message updates during streaming. - Adjust Files UI error presentation so upload-sheet errors are not dismissed by a competing parent alert.
- Release bookkeeping updates (CHANGELOG/CONTRIBUTING) and expanded demo-release-check test cases + setup instructions.
| File | Description |
|---|---|
| Demo/DemoChat/Sources/UI/ResponsesChatDetailView.swift | Feed ExyteChat messages through ChatMessagesPacer instead of raw store messages. |
| Demo/DemoChat/Sources/UI/Files/FilesView.swift | Disable the shared error alert while the upload sheet is presented; extend alert helper API. |
| Demo/DemoChat/Sources/UI/DetailView.swift | Apply ChatMessagesPacer to conversation chat messages in the main chat detail view. |
| Demo/DemoChat/Sources/UI/ChatView.swift | Bind conversation list directly to store.conversations (remove debounced list). |
| Demo/DemoChat/Sources/UI/ChatMessagesPacer.swift | New pacing helper that emits at most one messages update per interval. |
| Demo/DemoChat/Sources/ResponsesStore.swift | Remove store-level debouncing and update last streamed turn immediately. |
| Demo/DemoChat/Sources/ChatStore.swift | Remove debounced conversations mechanism; publish conversations directly. |
| CONTRIBUTING.md | Update local API-breakage check example to 0.6.0. |
| CHANGELOG.md | Add 0.6.0 section/date and update compare links. |
| .claude/skills/demo-release-check/test-cases.md | Add Files test cases and update Misc section expectation. |
| .claude/skills/demo-release-check/SKILL.md | Make simulator targeting generic + add file seeding steps and device-agnostic guidance. |
Review effort: Lite
filesErrorAlert's setter cleared the store's error whenever the alert binding was set to false, even while isEnabled was false and the error was meant for another view. The current screens never hit this, since the upload sheet can't open while the alert is showing, but the modifier now keeps its promise on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Note
This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.
FilesStore kept one published error that FilesView, the upload sheet and FileDetailView all showed, so an error from one screen's request could show up on another, and one screen's alert could dismiss another's. FilesView's isEnabled flag only worked around that for the upload sheet. FilesStore's methods now throw the request's error, and each screen keeps its own error state and alert. A cancelled request, or one for a client that has since been replaced, still returns without throwing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
5 open issues
ContinuousClock.Instantvs.nowandTask.sleep(until:)usage is fragile and may not compile… · NewTask.sleep(until:)is not a standard overload in Swift Concurrency APIs (the standard form is… The class-level doc comment claims cancelled requests return without throwing, butretrieve(id:),… · New Thesetside of theisPresentedbinding clearsstore.erroreven whenisEnabledisfalse.… Removing the debounced conversations pipeline meansconversationswill now publish on every…
Review effort: Lite
FilesStore's doc says a cancelled request returns without throwing, but only list and upload skipped cancellation errors. retrieve, content and delete threw them too, so a screen could show "cancelled" as a failed request. They now return nil/false on a cancellation as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Note
This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.
Copilot's reviews on this branch requested the runner group ltsh-staging-standard-4vcpu, which never picked them up: they waited in the queue until cancelled, and Copilot fell back to a lighter review after about 17 minutes. Earlier reviews ran on ubuntu-latest in a few minutes. copilot-code-review.yml pins its runner to ubuntu-latest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot code review kept requesting the ltsh-staging-standard-4vcpu runner group with the file on this branch, so it doesn't take effect from a pull request's branch. Whether the runner comes from MacPaw's organization settings is being checked first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Note
This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
4 open issues
.nowis unlikely to resolve to aContinuousClock.Instanthere (and may fail to compile… · New.nowis unlikely to resolve to aContinuousClock.Instanthere (and may fail to compile… · New The new pacing logic is non-trivial (throttling + coalescing to latest). Adding unit tests for… · New Cancellation is currently swallowed (try?) and the task still applies pending messages even if… · New
Review effort: Lite
When a test needs to control a type's dependency, pass it in through the initializer instead of test-only hooks or loosening the type's access control. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pacer takes its minimum interval in the initializer, so tests can use a short one. The views pass ChatMessagesPacer.chatViewUpdateInterval. The tests check that the first update is applied right away, that updates within the interval are held back and then applied once, as the latest messages, and that an update after the interval is applied right away. sleepUntil moves out of FilesStoreTests so both suites can use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Propagate an error, ignore an expected one explicitly by its type or code, or at least log it in the project's preferred way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The task waiting to pass on held-back messages held the pacer strongly, so the pacer stayed alive until the wait ended, and try? let a cancelled wait pass the messages on anyway. The task now holds the pacer weakly, deinit cancels it, and a cancelled wait returns without passing anything on. Any other error from the wait is logged, and the messages are passed on anyway. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
9 open issues
.nowis unlikely to resolve to aContinuousClock.Instanthere (and may fail to compile….nowis unlikely to resolve to aContinuousClock.Instanthere (and may fail to compile… This example uses0.6.0, but until the tag exists contributors running the command verbatim will… · New The pacing behavior (first update immediate, subsequent updates coalesced to the latest at… · NewshowingErrorassigns to@State(self.error = error). Some call sites invoke it from an… · NewTask { ... }is not guaranteed to run on the MainActor, but this closure mutates@State… · New Cancellation is currently swallowed (try?) and the task still applies pending messages even if… The new pacing logic is non-trivial (throttling + coalescing to latest). Adding unit tests for…updateTaskis an unstructured task retained by the pacer, but there’s no cancellation on… · New
Review effort: Lite
A reply that points to a commit still on the local machine refers to code the reviewer can't see yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A concrete version had to be bumped in every release, and named a tag that doesn't exist yet between merging the release and tagging it. The command now says <latest tag>, found on the Releases page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
5 open issues
Using angle brackets in a shell command is easy to accidentally copy/paste and run; many shells… · New Similar to other views, thisTask { ... }body can resume off the main actor after awaiting a… · NewshowingErrorupdates@State(self.error) but is not explicitly MainActor-isolated; several… · New Mutating@State(isUploading,error) and callingdismiss()from inside an unqualified `Task… · New This helper can loop indefinitely if the condition never becomes true, which can hang a test until… · New
9 resolved since last review
.nowis unlikely to resolve to aContinuousClock.Instanthere (and may fail to compile….nowis unlikely to resolve to aContinuousClock.Instanthere (and may fail to compile…Task { ... }is not guaranteed to run on the MainActor, but this closure mutates@State…showingErrorassigns to@State(self.error = error). Some call sites invoke it from an… The pacing behavior (first update immediate, subsequent updates coalesced to the latest at… This example uses0.6.0, but until the tag exists contributors running the command verbatim will… Cancellation is currently swallowed (try?) and the task still applies pending messages even if… The new pacing logic is non-trivial (throttling + coalescing to latest). Adding unit tests for…updateTaskis an unstructured task retained by the pacer, but there’s no cancellation on…
Review effort: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Conversation deletion can invalidate indices retained by active requests, causing crashes or updates to the wrong conversation.
6 open issues
Use stable conversation IDs for in-flight assistant updates · New This helper can loop indefinitely if the condition never becomes true, which can hang a test until… Mutating@State(isUploading,error) and callingdismiss()from inside an unqualified `Task…showingErrorupdates@State(self.error) but is not explicitly MainActor-isolated; several… Similar to other views, thisTask { ... }body can resume off the main actor after awaiting a… Using angle brackets in a shell command is easy to accidentally copy/paste and run; many shells…
Review effort: Balanced
A reply's requests keep a conversation's position in `conversations` across awaits. Since the list binds to `conversations` directly, deleting an earlier conversation during a reply shifted that position: the next chunk crashed on an out-of-range index, or went to another conversation. The list now asks the store to delete, and `conversations` can only be changed by the store. `deleteConversation` throws while a request is in flight or an assistant run is being polled, and the chat screen shows that in an alert, keeping the conversation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It used to wait until the suite's one-minute time limit when its condition never held, and the failure didn't say what it was waiting for. It now fails after 5 seconds by default, at the line that called it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reference is never reassigned, which the compiler warns about. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The Demo lifecycle changes warrant final human validation, particularly the live-file flow and detail-screen error alert that remain untested.
5 resolved since last review
This helper can loop indefinitely if the condition never becomes true, which can hang a test until… Mutating@State(isUploading,error) and callingdismiss()from inside an unqualified `Task…showingErrorupdates@State(self.error) but is not explicitly MainActor-isolated; several… Similar to other views, thisTask { ... }body can resume off the main actor after awaiting a… Using angle brackets in a shell command is easy to accidentally copy/paste and run; many shells…
Review effort: Balanced
Adds the streamed-reply lag and conversation deletion fixes to 0.6.0's Fixed section, as earlier releases list Demo fixes there too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Release bookkeeping ahead of tagging 0.6.0, plus Demo fixes found while checking it and a few instructions for agents.
Release
[Unreleased]becomes[0.6.0] - 2026-10-27: the Files API (Feat: Add Files API endpoints #401) and the Demo Files screen (Feat: Add a Files screen to the Demo app #470). Fixed also lists this PR's Demo fixes for streamed replies lagging behind the stream and for conversation deletion. Compare links are updated.OpenAIProtocolgained afilesrequirement, an accepted break, and CONTRIBUTING ships accepted breaks in a minor release.<latest tag>instead of a version, pointing to the Releases page. It no longer names a tag before it exists, and it no longer needs a bump in each release.Demo release check (
.claude/skills/demo-release-check)[smoke]lists files and filters by purpose, X-09 checks the OpenAI-only screen, X-10 uploads, downloads and deletes a file, and X-11 checks a failed upload. X-01 now expects the Files section in Misc.Demo fixes
FilesStoreused to publish oneerrorthat the Files list, the upload sheet and the file details all showed, so a failed upload also opened the list's alert, which dismissed the sheet and took the sheet's alert and the error with it. The store's methods now throw, and each screen keeps its own error and alert. A cancelled request, or one for a replaced API configuration, still isn't reported as an error.ChatViewapplies every message change as a separate animated table update (~465 ms each), in a serial queue that doesn't merge them, while the stores published a snapshot every 100 ms. With a web search reply in Responses, 39 updates were still queued when the stream finished and the last one landed 15.5 s later. Pacing now lives in the views (ChatMessagesPacer): at most one update every 500 ms, always the latest messages. The same reply now leaves at most one update queued, and the last lands ~0.2 s after the stream finishes. The stores publish every change right away and know nothing about the chat view.ChatMessagesPacertakes its interval in its initializer and has its own tests. A held-back update no longer keeps the pacer alive, and the pacer cancels it when it goes away.updateTransactioncan't be used to pace updates or turn their animation off: it returns before the table receives the update, and only applies its animation mode to some updates (16 of 26 in a test). An upstream issue is being drafted.ChatStoreno longer keeps a delayed copy of its conversations, so deleting a conversation removes it from the store. The list asks the store to delete instead of editing the conversations through a binding, and the store refuses while a reply is in progress, which the chat screen shows in an alert. A reply's requests refer to their conversation by its position in the list, so deleting an earlier conversation mid-reply would crash the app or send the reply to another conversation.Agent instructions (
CLAUDE.md)try?: propagate them, ignore an expected one explicitly by its type or code, or log them.Testing
swift build,swift test(246 XCTest + 35 Swift Testing tests) and the API breakage check against 0.5.3 pass; the six reported breaks match the allowlist exactly. These ran on the first CHANGELOG commit; later commits only touch Demo, the release-check skill,CLAUDE.md,CONTRIBUTING.mdandCHANGELOG.md.After merging
0.6.0(adjust the CHANGELOG date if it's tagged on another day) and publish a GitHub Release with a call-out about theOpenAIProtocol.filesbreak at the top..github/api-breakage-allowlist.txt.🤖 Generated with Claude Code