Skip to content

Prepare the 0.6.0 release - #475

Merged
nezhyborets merged 23 commits into
mainfrom
release-prep-0.6.0
Oct 27, 2026
Merged

nezhyborets merged 23 commits into
mainfrom
release-prep-0.6.0

Conversation

@nezhyborets

@nezhyborets nezhyborets commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Release bookkeeping ahead of tagging 0.6.0, plus Demo fixes found while checking it and a few instructions for agents.

Release

  • CHANGELOG: [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.
  • Version: 0.6.0 rather than 0.5.4, because OpenAIProtocol gained a files requirement, an accepted break, and CONTRIBUTING ships accepted breaks in a minor release.
  • CONTRIBUTING: the local API breakage check now says <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.
  • API breakage allowlist: unchanged. Its six entries are breaks against 0.5.3, which CI compares with until 0.6.0 is tagged; empty it after tagging.

Demo release check (.claude/skills/demo-release-check)

  • New Files cases: X-08 [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.
  • The skill puts a test file in the simulator's Files app (On My iPhone), so the document picker can be driven.
  • It no longer needs an "iPhone 17" simulator: it builds for the generic iOS Simulator and boots any available iPhone.

Demo fixes

  • Files errors: a failed upload now keeps the upload sheet open and shows the error there. FilesStore used to publish one error that 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.
  • Streamed replies lagging behind the stream: ExyteChat's ChatView applies 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.
    • ChatMessagesPacer takes 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.
    • ExyteChat's updateTransaction can'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.
    • ChatStore no 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.
    • The conversation list isn't paced and redraws on every streamed change. Move the Demo app from ObservableObject to the Observation framework #476 moves the Demo to the Observation framework first; how often the UI updates gets revisited after that.

Agent instructions (CLAUDE.md)

  • Make a type testable by injecting what the test needs to control through its initializer, instead of test-only hooks or making private members less private.
  • Don't discard errors with try?: propagate them, ignore an expected one explicitly by its type or code, or log them.
  • Reply to a review comment about a code change only once that change is pushed.

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.md and CHANGELOG.md.
  • Demo builds, and the DemoChat tests pass (65).
  • Demo release check, smoke scope: 6/6 pass (L-01, C-01, C-04, R-01, X-02, X-08) on an iPhone 17 Pro Max simulator with iOS 27.0, run before the streaming change. After it: a web search reply in Responses, a long reply in Chats, reopening a conversation and deleting one, all by hand.
  • X-11 (failed upload) passes after the Files error change, along with the Files list's own error alert.
  • Not run with a real key yet: X-10 (upload, download and delete). The file details screen's error alert needs a real file, so it's untested too.

After merging

  • Tag the merge commit 0.6.0 (adjust the CHANGELOG date if it's tagged on another day) and publish a GitHub Release with a call-out about the OpenAIProtocol.files break at the top.
  • Then empty .github/api-breakage-allowlist.txt.

🤖 Generated with Claude Code

nezhyborets and others added 7 commits October 22, 2026 20:55
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>
@nezhyborets
nezhyborets requested review from Krivoblotsky and a balanced review from Copilot October 23, 2026 23:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ChatMessagesPacer and 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

Comment thread Demo/DemoChat/Sources/ChatStore.swift Resolved
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>
@nezhyborets
nezhyborets requested a balanced review from Copilot October 25, 2026 19:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@nezhyborets
nezhyborets requested a balanced review from Copilot October 25, 2026 19:53
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Review effort: Lite

Comment thread Demo/DemoChat/Sources/FilesStore.swift Resolved
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>
@nezhyborets
nezhyborets requested a lite review from Copilot October 25, 2026 23:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@nezhyborets
nezhyborets requested a balanced review from Copilot October 25, 2026 23:27
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@nezhyborets
nezhyborets requested a balanced review from Copilot October 26, 2026 17:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Review effort: Lite

nezhyborets and others added 4 commits October 26, 2026 19:52
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>
@nezhyborets
nezhyborets requested a balanced review from Copilot October 26, 2026 21:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread CONTRIBUTING.md Resolved
nezhyborets and others added 2 commits October 27, 2026 00:39
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>
@nezhyborets
nezhyborets requested a balanced review from Copilot October 26, 2026 22:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
9 resolved since last review

Review effort: Lite

Comment thread CONTRIBUTING.md Resolved
Comment thread Demo/DemoChat/Tests/SleepUntil.swift Resolved

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread Demo/DemoChat/Sources/UI/ChatView.swift Resolved
@marushkaa
marushkaa requested a balanced review from Copilot October 27, 2026 09:14
@nezhyborets
nezhyborets removed the request for review from Copilot October 27, 2026 09:14
nezhyborets and others added 4 commits October 27, 2026 14:56
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@nezhyborets
nezhyborets merged commit 0e1a00c into main Oct 27, 2026
12 checks passed
@nezhyborets
nezhyborets deleted the release-prep-0.6.0 branch October 27, 2026 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants