* fix(auto-reply): keep suppressed reply text preview truncation UTF-16 safe
String.prototype.slice at offset 160 can split surrogate pairs in
reply text previews logged for suppressed auto-replies. Replace raw
slice(0, 160) with truncateUtf16Safe — already imported in this file.
* test(auto-reply): exercise suppressed preview logging
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(acp): use truncateUtf16Safe for event mapper error text truncation
.slice(0, 100) on ACP event argument values can split surrogate
pairs from emoji or CJK text, producing U+FFFD in error/warning
notices shown to operators.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(acp): cover UTF-16-safe tool titles
* refactor(acp): keep event mapper import leaf-only
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(gateway): keep plugin approval title and description truncation UTF-16 safe
Node.js String.prototype.slice cuts at UTF-16 code unit boundaries,
which can split surrogate pairs (emoji, CJK supplementary characters)
into lone surrogates that render as U+FFFD in approval UI payloads.
Use truncateUtf16Safe for both title and description fields in
createPluginApprovalsAttachment so approval requests never deliver
broken characters to the gateway approval UI.
Fixes #???
* test(gateway): exercise UTF-16 approval payloads
---------
Co-authored-by: wm0018 <wu.min5@xydigit.com>
* fix(session-cost-usage): keep emoji / surrogate pairs intact during content truncation
.slice(0, maxLen) counts UTF-16 code units, so an emoji straddling
the cut point produces a lone surrogate that renders as � in
usage cost CLI output. Use [...str] to count full code points instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(usage): cover UTF-16-safe log truncation
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): use truncateUtf16Safe for attempt notification text truncation
.slice(0, 237) on notification text can split surrogate pairs from
CJK characters or emoji in agent output, producing U+FFFD in Codex
desktop notifications.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(codex): cover UTF-16-safe attempt previews
* test(codex): assign notification coverage to a shard
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(android): render LaTeX display math in chat via bundled KaTeX
* fix(android): preserve fallback for invalid math
* docs(changelog): note Android display math
* Revert "docs(changelog): note Android display math"
This reverts commit 40356286a3.
* fix(status): keep issue message truncation UTF-16 safe
String.prototype.slice at offset 90 can split surrogate pairs in issue
titles displayed by `openclaw status --all`, rendering broken U+FFFD in
the channel issues column.
Replace raw slice(0, 90) with truncateUtf16Safe to match the existing
UTF-16-safe truncation hardening pattern.
Fixes #???
* test(status): cover UTF-16-safe issue rendering
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(ui): redesign dashboard chrome with tiny top bar and sidebar search
Move the sidebar toggle to the top bar as a macOS-style panel button
(collapse on desktop, drawer on tablet/mobile), move search into a
sidebar field that opens the command palette, and move the color-mode
toggle into the sidebar footer. Shrink the top bar to 44px, make the
terminal toggle a quiet ghost button, and drop the shadow plate behind
the macOS titlebar brand mark so the transparent mascot renders bare.
Adds the missing common.colorModeOption i18n key (theme buttons were
announcing the raw key) and removes the dead theme-orb styles.
* fix(ui): translate common.colorModeOption across locale bundles
The i18n sync initially recorded English fallbacks for the new key,
which the ships-no-recorded-English-fallbacks gate rejects. Regenerated
with a real translation provider; all 20 locales now carry translated
values (fallbacks=0).
* refactor(ui): unify the topbar sidebar toggle into one button
One button drives both modes: rail collapse on desktop and the
slide-over drawer at the ≤1100px breakpoint, decided via matchMedia
(with the legacy addListener fallback used elsewhere in bootstrap).
Replaces the previous pair of CSS-swapped buttons.
* ci(mantis): add web ui chat proof lane
* ci(mantis): tighten web ui proof candidate parsing
* ci: tighten Mantis Web UI proof lane
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
sessions.delete gains an explicit archivedOnly param (additive protocol
schema change, Swift models regenerated): with archivedOnly=true, the
dispatcher grants operator.write and the handler requires the target to
already be archived (archive-then-delete keeps destructive intent a
two-step action). Without the flag nothing changes: deletes require
operator.admin, so internal fallback/synthetic dispatch, subagent cleanup,
and CLI minting keep their admin contracts, and the session-kill HTTP
endpoint pins admin explicitly since it terminates live runs.
Android sends archivedOnly and offers Delete only on archived rows, where
its bounded (non-admin) operator session is now authorized; active rows
archive first. iOS/web connect with admin and keep unrestricted deletes.
Refs #100712