* feat(ios): adopt gateway user accent in chat
The iOS chat surface previously always used the hardcoded brand accent.
Read ui.prefs.accent ?? ui.seamColor (the Control UI user-accent
contract) from the existing config.get branding refresh and feed it to
the shared chat kit's userAccent seam, matching the macOS chat window.
Invalid values fall through; accent resets on gateway switch.
* fix(ios): contrast-aware accent ink and live config.changed refresh
Address ClawSweeper review findings: the shared chat kit now derives
user-text/send-glyph ink from the accent via the Control UI WCAG rule
(relative luminance > 0.179 -> black), fixing unreadable light accents
on iOS and macOS alike; the iOS server-event switch routes
config.changed through the guarded branding refresh so an accent
change reaches a connected app without reconnect.
* feat(ui): add mobile desktop document mode
Add a shell-free mobile desktop route that reuses the dock panel controller and lazy noVNC client, with source preselection, touch controls, keyboard input, and retryable inventory failures.
* feat(ios): add desktop viewer entry points
* feat(android): add desktop viewer
* fix(android): keep System Back inside the desktop viewer
The per-session viewer replaces SessionDashboardScreen in place instead of
pushing a shell tab, so System Back fell through to the shell-level handler
and popped the whole Dashboard tab, stranding the operator on Chat. Claim
Back while the viewer is showing.
Also carry over TerminalSettingsScreen's imePadding: the viewer's own touch
toolbar hosts the keyboard affordance, so the soft keyboard would cover it.
Proof (emulator, Medium_Phone_API_36.0, stub control UI on 18789):
pre-fix Back from the viewer lands on Chat; post-fix it returns to Dashboard.
No Robolectric regression test — no existing screen test constructs
MainViewModel, and BackHandler under Robolectric would need new scaffolding
for weaker evidence than the live repro.
* test(ui): stop the pairing views leaking dialogs into the shared document
`ui/vitest.config.ts` runs the unit project with `isolate: false`, so test files
share one jsdom document inside a worker. `view.pairing.test.ts` appends a
container to `document.body` for every case and never tears down, unlike its
sibling `channels-page.test.ts`, so whichever suite the worker scheduled next
inherited a mounted pairing dialog.
That surfaced on this PR's first CI run as ten failures in the untouched
`input-dialog.test.ts`, which found "Approve DM access" where it expected
"Rename session". A rerun went green, so the ordering is scheduler-dependent
rather than deterministic; this removes the contamination source rather than
leaving the next suite to lose the race.
Not a proven fix for that specific run — the leak reproduces only under CI's
file scheduling, and the full suite passes locally either way — but the missing
teardown is a real violation of the shared-environment contract.
* test(ui): stop the background-tasks rail asserting on a ticking clock
The rail e2e captured the main transcript's text before opening a task detail
and required it to be byte-identical afterwards. A running task renders a live
elapsed label, so the assertion failed whenever a second ticked over between
the two reads — twice while landing this PR, both times "12s" against "13s"
with no other difference.
Normalize elapsed labels on both sides instead of weakening the assertion. The
invariant it protects, that opening a detail leaves the main transcript alone,
still holds: a real content change is still caught, and only complete duration
tokens collapse, so diffstat figures like +14/-3 and phrases like "5 messages"
are untouched.
* fix(thinking): fall back instead of erroring on unsupported adaptive level (#109351)
The adaptive level is the auto sentinel (normalizeThinkLevel maps auto ->
adaptive) meaning 'pick an appropriate level', not 'force this exact level'.
Some clients (e.g. the Apple Watch client) send it as a transport default
while the iOS app sends none, so hard-erroring on an unsupported adaptive
override rejected otherwise-valid turns from those clients. Now the auto
sentinel always falls back to a supported level; explicit concrete levels
(e.g. an explicit /think xhigh) still hard-error as before.
Closes#109351
* docs(proof): add real-behavior-proof screenshot + run log for #111301
Captured run of the real get-reply-run.media-only test (101 passed)
showing the adaptive-thinking fallback fix verified.
Co-Authored-By: yuvrajlaptop2008-byte <284412773+yuvrajlaptop2008-byte@users.noreply.github.com>
* fix(ios): inherit Watch chat thinking defaults
Co-authored-by: yuvraj thakur <284412773+yuvrajlaptop2008-byte@users.noreply.github.com>
* chore(ios): sync native i18n inventory
---------
Co-authored-by: yuvrajlaptop2008-byte <284412773+yuvrajlaptop2008-byte@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(ios): render plugin approvals as native Allow/Deny notifications
Extend the iOS approval handling from exec to plugin approvals so the native
push the gateway now sends (#108505) shows a phone notification with Allow/Deny.
Extract a shared ApprovalNotificationBridge with exec/plugin wrappers owning
their own kind constants and category; tag the parsed prompt with its kind;
register the plugin category and route plugin pushes in the app delegate; add a
.plugin case that maps the plugin presentation (title/description/pluginId/
severity) into the existing prompt and widens the exec-only validation, current,
cache-restore, and resolve guards to accept plugin; parameterize the dialog copy
by kind. Watch snapshot publication stays exec-only; the phone notification still
mirrors to the watch via WatchConnectivity. Reuses the unified approval.get/
approval.resolve RPCs (already kind-generic); no server or protocol changes.
* chore(i18n): sync native inventory after rebase
* chore(ios): drop accidentally committed local Version.xcconfig build override
* fix(ios): sync Apple i18n catalog and remove dead approval-bridge methods
Sync apps/ios/Resources/Localizable.xcstrings for the new plugin-approval
strings (native inventory sync alone did not cover the Apple catalog).
Remove the superseded per-bridge registerCategory variants and the redundant
exec parseResolvedPush wrapper flagged by the iOS Periphery dead-code scan;
registration uses ApprovalNotificationBridge.registerCategories and the shared
parse helpers.
* chore(ios): drop generated SwiftSources filelist; reconcile native i18n inventory
* chore(ios): drop generated Xcode project from tracking
Codex generated OpenClaw.xcodeproj for its simulator test run and the initial
apps/ios directory commit force-added it; xcodegen regenerates it in CI, so it
must not be tracked (matches main).
* chore(ios): reconcile native i18n inventory after rebase