iOS showed scheduled messages but could not create them. A clock
button now sits next to the composer's attachment button. It offers
the web presets (in 5 min, 30 min, 1 h, 4 h) or a specific time up to 7
days ahead. A chip above the input shows the pending time; its × sends
normally again.
Presets resolve when the user taps Send, so "in 5 minutes" counts from
the send. Validation follows the web: a 30 s grace period for past
times and a 7-day maximum.
A scheduled send is text-only and queued. The hub rejects scheduledAt
with attachments or steer, so the clock is disabled while attachments
are present and the steer menu is hidden. The schedule clears once the
send dispatches. Sending reuses the existing optimistic path, and a
retry keeps the row's scheduledAt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Typing / in the iOS composer offered nothing; web and Android show the
session's commands. While the input is a lone /token, a list above the
card shows matching commands. A tap inserts "/name " so the user can
add arguments.
As on Android, the sources are metadata.slashCommands plus the
/slash-commands RPC. The RPC is fetched once, when the input first
enters slash mode, and its entries override bare names. A failed fetch
retries on the next /. There is no copy of the shared built-in table,
so the CLI stays the source of truth. Ranking follows web: exact,
prefix, contains, then near-miss typos. The list is hidden in
Scratchlist mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Use the split navigation on foldable iPhones once UIHingeInteraction
(iOS 27.1+) reports a hinge: unfolded shows sidebar + chat, folded
collapses to the phone stack without recreating the chat.
- Side-by-side sidebar: drop the truncated inline "Sessions" title and
move the filter beside the hub menu (also fixes iPad).
- Folded outer screen: drop the home title so the empty bar disappears,
use the leading-aligned system title/subtitle in chat, minimize the
chat title on scroll, and let the composer sit 16pt closer to the home
indicator while the text field is unfocused.
- Qualify Markdown.Document, which is ambiguous with the iOS 27.1 SDK.
- test-transcript.sh picks the newest runtime that supports the device
type (iOS 27.1 only ships iPhone Duo); split tests run on iPhone Duo.
Add current-proposal decoding, dedicated implementation requests and composer-only continuation on iOS and Android. Keep pending/errors across row recycling, reject stale actions and prevent duplicate submissions.
Include native regression tests and localized plan controls.
Use unified 16pt Dynamic Type body text, clearer Markdown spacing, and a shared reading column. Preserve transcript anchors across typography changes and bound tool command previews to two lines. Add layout and typography regression tests.
Interaction layer turning the read-only chat into a working remote control,
mirroring the merged Android B-M3ab feature-for-feature (web authority where
the ports disagreed):
- ChatInteractor (HapiClient, fully swift-test covered): optimistic composer
sends (appendOptimistic -> POST -> status settle), queue-by-default with a
long-press Send&Steer intent while a turn is active, tap-to-retry on failed
rows (steer retries degrade to queue), per-session drafts
(UserDefaultsChatDrafts, hub-scoped keys, debounced + flushed on close).
- session_inactive (409) recovery: one POST /resume (current permissionMode)
then retry; a superseding session id seeds the new window
(MessageWindowControllers.seed), migrates the draft, retargets the
optimistic row and emits sessionSuperseded -- ChatView/HomeView replace the
navigation entry in place.
- Queued bar: uninvoked sends in web sort order with Cancel (optimistic
DELETE; invoked-race ingests the authoritative row as sent), Edit
(cancel + composer prefill, newer-draft guard) and Steer (invoked answers
reconcile a missed consume); single-flight per-row op guard.
reconcileQueuedState now runs on chat open and on session-pipe gap.
- Permission actions: flavor-exact bodies mirroring PermissionFooter.tsx --
claude {} / allowTools (Bash(cmd)) / mode:acceptEdits, codex-family
decision approved / approved_for_session / abort via deny -- plus
AskUserQuestion flat answers (option cards, Other free text, no-questions
fallback, cursor stable ids) and request_user_input nested answers
(user_note suffix, required validation); optimistic Resolving /
AlreadyHandled (404/409) overrides settled by the agentState patch.
ChatPipeline now re-attaches the window row's client status so failed
user rows actually render the retry affordance (web normalize.ts parity;
the Android reference misses this overlay).
- Session config sheet (toolbar gear): catalog-driven permission-mode picker
with tones, claude static model/effort catalogs (ModelCatalog port), codex
models via new GET /sessions/:id/codex-models endpoint + wire types with
per-model reasoning efforts; optimistic detail updates
(SessionListStore.updateDetailLocal, new) rolled forward to server truth
on error.
- Lifecycle: VisibilityReporter posts POST /api/visibility per tracked
handshake subscriptionId on scene-phase flips (404 prunes); ChatSession
exposes its subscriptionId and feeds the reporter; the global SSE pipe was
already HubSession-lifetime on iOS.
- Tests: ChatInteractorTests transcribes the Android interaction suite
against the real APIClient/AuthManager/SessionListStore/window registry
with only HTTP scripted -- canonical JSON bodies asserted byte-for-byte
(send/approve/deny/config), optimistic send happy/fail/retry, 409 resume
both id paths, cancel invoked-race, steer reconcile, edit prefill,
override lifecycle, config optimistic + rollback, drafts, abort.