Skip to content

fix(control-ui): persist assistant avatar override locally#71639

Merged
BunsDev merged 4 commits into
mainfrom
fix/assistant-avatar-local-override
Apr 25, 2026
Merged

fix(control-ui): persist assistant avatar override locally#71639
BunsDev merged 4 commits into
mainfrom
fix/assistant-avatar-local-override

Conversation

@BunsDev

@BunsDev BunsDev commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Mirrors the user-avatar pattern: assistant avatar uploads now persist to localStorage instead of routing through config.patchui.assistant.avatar. The Quick Settings uploader stops failing with invalid config: ui.assistant.avatar: Too big: expected string to have <=200 characters when the data URL exceeds the schema cap.
  • Bootstrap (control-ui-bootstrap.ts) and loadAssistantIdentity overlay the local override on top of the gateway-resolved identity so the override always wins — same precedence rule the chat already uses for the user avatar.
  • Lifts the gateway-side ui.assistant.avatar zod cap from 2002_000_000 to match the user-avatar size budget for non-Control-UI clients (agents.list identity etc.) writing the field directly. UI assistant-identity normalizer split into a 64-char text branch and a 2 MB image-URL/path branch, mirroring user-identity.ts.
  • Collapses the avatar upload handler in app-render.ts from a 44-line async/loadConfig/loadAssistantIdentity dance into a synchronous setter.
  • .gitignore: ignore .vmux* worktree paths.
  • Changelog entry under ### Fixes with Thanks @BunsDev attribution.

Security review

  • XSS: Same surface as the existing user-avatar upload (<input type=file>FileReader.readAsDataURL<img src=data:…>). SVG-as-image is sandboxed by browsers; no script execution path. No new vector.
  • Quota: 1.5 MB upload cap (already enforced) → ~2 MB after base64. localStorage typically allows 5–10 MB per origin. try/catch covers QuotaExceededError. Same as user avatar.
  • Server attack surface: Net reduction — one fewer authenticated config.patch path writing to gateway config.
  • Privacy: Avatar data stays client-side. Improvement over the round-trip-to-gateway model.
  • Auth bypass / prompt injection: N/A — purely client-side persistence; no auth or prompt surfaces touched.

Test plan

  • pnpm test:changed (4,737 tests across gateway / runtime-config / ui lanes — green)
  • pnpm tsgo:core and pnpm check:test-types clean on touched files
  • pnpm lint:core clean
  • pnpm build clean (UI bundle + gateway artifacts regenerated)
  • New unit coverage:
    • ui/src/ui/assistant-identity.test.ts — data-URL passthrough, path passthrough, short-text accept, long-text reject, newline reject
    • ui/src/ui/controllers/assistant-identity.test.ts — local persistence + clear-override
    • src/gateway/assistant-identity.test.ts — 50 KB+ data URL passthrough through coerceIdentityValue
  • Manual: Quick Settings → Personal → Assistant avatar uploader, picked an image, confirmed it renders without the 200-char rejection.

🤖 Generated with Claude Code

BunsDev and others added 4 commits April 25, 2026 10:05
Pair Appearance with Automations and let Channels stand alone in the
middle column so all three top-row columns reach similar heights.
Promote Personal to a full-width row with a horizontal body
(identity tiles | emoji + actions) so the avatar block stops fighting
for half-width space. Drops the unused .qs-stack--wide hook.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
…istant identity pair

Restructure Personal card layout to present User and Assistant as 2 balanced identity cards instead of separate User tile + form controls. Mirrors the visual hierarchy and UI pattern across both identities.

Changes:
- Move User avatar text input into User identity card's .__repair section (mirroring Assistant's structure)
- Inline "Choose image" and "Clear avatar" buttons as flex-wrapped action group
- Remove .qs-personal-body and .qs-personal-form wrapper divs
- Update Personal card's .qs-identity-grid to 2-column layout with balanced spacing
- Responsive collapse to 1-column at ≤760px

Tests:
- config-quick.test.ts updated to expect 2 stacks (no longer wrapping Personal in form)
- config-quick.test.ts validates identity card layout now has symmetric User↔Assistant structure
- All 10 quick settings view tests passing
- All 20 schema regression tests passing

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
… via gateway config

Mirrors the user-avatar pattern: assistant avatar uploads now go to
localStorage and overlay the gateway-resolved identity at bootstrap and on
agent.identity.get refreshes. Sidesteps the ui.assistant.avatar zod cap
that rejected uploaded data URLs as 'Too big: expected string to have
<=200 characters', removes one config.patch RPC from the avatar path, and
collapses the upload handler from a 44-line async/loadConfig dance into a
plain synchronous setter.

Also lifts the gateway-side ui.assistant.avatar schema cap from 200 to
2,000,000 to match the user-avatar size budget for non-UI clients writing
the field directly, and adds a content-aware text/image normalizer in
ui/src/ui/assistant-identity.ts so short-text avatars stay short while
data URLs survive round-tripping.
@aisle-research-bot

aisle-research-bot Bot commented Apr 25, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Unrestricted assistant avatar URLs allow SVG data URLs and arbitrary same-origin paths (XSS/CSRF risk)
2 🟡 Medium Unbounded assistant avatar persisted in localStorage can cause persistent client-side DoS
1. 🟡 Unrestricted assistant avatar URLs allow SVG data URLs and arbitrary same-origin paths (XSS/CSRF risk)
Property Value
Severity Medium
CWE CWE-79
Location ui/src/ui/chat/chat-avatar.ts:95-100

Description

The UI normalizes and preserves assistant avatar values that match data:image/* or any same-origin absolute path (/...) and later renders them directly into an <img src> attribute.

  • normalizeAssistantAvatar() accepts any string matching /^(data:image\/|\/(?!\/))/i without restricting MIME type (e.g., data:image/svg+xml,...) or allowed path prefixes.
  • renderChatAvatar() uses the normalized value directly as <img src="${assistantAvatar}"> with no additional sanitization.

This creates risk that a crafted avatar value can:

  • Trigger script execution or HTML injection via SVG image payloads in browsers/environments where SVG-in-<img> scripting is possible or via future rendering changes.
  • Force authenticated cross-origin/same-origin GET requests to arbitrary application endpoints by setting the avatar to a same-origin path (e.g., /api/...), which can be abused for CSRF-style effects if any state-changing GET endpoints exist.

Vulnerable code paths:

// ui/src/ui/assistant-identity.ts
const RENDERABLE_AVATAR_URL_RE = /^(data:image\/|\/(?!\/))/i;
...
if (RENDERABLE_AVATAR_URL_RE.test(trimmed)) {
  return trimmed;
}// ui/src/ui/chat/chat-avatar.ts
return html`<img class="chat-avatar ${className}" src="${assistantAvatar}" alt="${assistantName}" />`;

Recommendation

Constrain assistant avatar URLs to a safe subset and reuse the existing URL-safety logic.

  1. Block SVG data URLs (and optionally all data: except a known-safe allowlist of raster types).
  2. Restrict same-origin paths to a narrow allowlist (e.g., only /avatar/ routes), rather than allowing any /(?!/).
  3. Apply validation at the point of normalization (so all renderers benefit).

Example hardening:

// ui/src/ui/assistant-identity.ts
const ALLOWED_DATA_IMAGE_MIME = new Set(["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif"]);

function isAllowedDataImage(url: string): boolean {
  const trimmed = url.trim();
  if (!trimmed.toLowerCase().startsWith("data:")) return false;
  const comma = trimmed.indexOf(",");
  if (comma < 5) return false;
  const meta = trimmed.slice(5, comma);
  const mime = meta.split(";")[0].toLowerCase();
  return ALLOWED_DATA_IMAGE_MIME.has(mime);
}

function isAllowedSameOriginAvatarPath(p: string): boolean {
  return p.startsWith("/avatar/");
}

function normalizeAssistantAvatar(value: string | null | undefined): string | null {
  const trimmed = coerceIdentityValue(value ?? undefined, MAX_ASSISTANT_IMAGE_AVATAR);
  if (!trimmed) return null;
  if (trimmed.startsWith("data:")) return isAllowedDataImage(trimmed) ? trimmed : null;
  if (trimmed.startsWith("/")) return isAllowedSameOriginAvatarPath(trimmed) ? trimmed : null;
  ...
}

This prevents SVG-based payloads and avoids turning the avatar field into a general-purpose same-origin request gadget.

2. 🟡 Unbounded assistant avatar persisted in localStorage can cause persistent client-side DoS
Property Value
Severity Medium
CWE CWE-400
Location ui/src/ui/storage.ts:316-338

Description

The Control UI persists an assistant avatar override directly to localStorage without any normalization, size limit, or scheme/mime restriction.

  • Input: setAssistantAvatarOverride(..., avatar) accepts an arbitrary string and calls saveLocalAssistantIdentity({ avatar }).
  • Persistence: saveLocalAssistantIdentity stores the value verbatim under openclaw.control.assistant.v1.
  • Replay on startup: loadLocalAssistantIdentity reads and JSON.parses the stored value on subsequent loads; controllers then force this value to override the server-provided identity.
  • Impact: A very large data:image/... URL (or simply a huge string) can be stored and then repeatedly parsed and applied on each load, potentially causing memory/CPU pressure, UI hangs, or repeated crashes (persistent client-side denial of service). This is especially relevant if any same-origin script execution occurs (e.g., an XSS elsewhere) because the payload can be made persistent across sessions.

Vulnerable code:

const parsed = JSON.parse(raw) as Partial<LocalAssistantIdentity>;
return { avatar: typeof parsed.avatar === "string" ? parsed.avatar : null };
...
storage?.setItem(LOCAL_ASSISTANT_IDENTITY_KEY, JSON.stringify({ avatar: next.avatar }));

Recommendation

Apply strict validation and size limits before saving and after loading local assistant identity, similar to normalizeAssistantIdentity.

Suggested approach:

  • Cap stored avatar length (e.g., 64 chars for text, ~2MB for image data URLs).
  • Only allow renderable/expected avatar formats (e.g., data:image/png|jpeg|webp|gif and same-origin /...), and consider explicitly rejecting data:image/svg+xml if you want to eliminate SVG-based script gadget risk.
  • If the stored value is invalid/too large, clear it from storage.

Example:

import { normalizeAssistantIdentity } from "./assistant-identity";

export function loadLocalAssistantIdentity(): LocalAssistantIdentity {
  const storage = getSafeLocalStorage();
  try {
    const raw = storage?.getItem(LOCAL_ASSISTANT_IDENTITY_KEY);
    if (!raw) return { avatar: null };// Hard cap raw size to avoid parsing huge blobs repeatedly
    if (raw.length > 2_100_000) {
      storage?.removeItem(LOCAL_ASSISTANT_IDENTITY_KEY);
      return { avatar: null };
    }

    const parsed = JSON.parse(raw) as Partial<LocalAssistantIdentity>;
    const normalized = normalizeAssistantIdentity({ avatar: parsed.avatar ?? null });
    return { avatar: normalized.avatar ?? null };
  } catch {
    return { avatar: null };
  }
}

export function saveLocalAssistantIdentity(next: LocalAssistantIdentity) {
  const storage = getSafeLocalStorage();
  try {
    const normalized = normalizeAssistantIdentity({ avatar: next.avatar ?? null });
    if (!normalized.avatar) {
      storage?.removeItem(LOCAL_ASSISTANT_IDENTITY_KEY);
      return;
    }
    storage?.setItem(
      LOCAL_ASSISTANT_IDENTITY_KEY,
      JSON.stringify({ avatar: normalized.avatar })
    );
  } catch {// best-effort
  }
}

Analyzed PR: #71639 at commit 6e6f7fc

Last updated on: 2026-04-25T15:45:39Z

@chatgpt-codex-connector

Copy link
Copy Markdown
Contributor

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime size: M maintainer Maintainer-authored PR labels Apr 25, 2026
@greptile-apps

greptile-apps Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the 200-character config validation error for assistant avatar uploads by persisting overrides to localStorage (mirroring the user-avatar pattern) and lifting the gateway-side ui.assistant.avatar schema cap to 2 MB. Two regressions are introduced in the new synchronous override flow:

  • When clearing an override, state.assistantAvatar is not reset to null in setAssistantAvatarOverride, so the stale data URL continues to drive configAssistantAvatar and effectiveAssistantAvatar until the page reloads.
  • assistantAvatarOverride (which gates the "Replace image" label and the "Clear override" button) is derived from the gateway config snapshot — which is no longer updated — so both UI states are permanently unreachable after the first upload.

Confidence Score: 3/5

Two P1 regressions make this unsafe to merge as-is: the 'Clear override' button is unreachable and the avatar is not visually cleared after a clear action.

Two independent P1 bugs: stale assistantAvatar on clear, and assistantAvatarOverride always null for new localStorage-based uploads. Together they mean the clear flow is both visually broken and unreachable from the UI.

ui/src/ui/controllers/assistant-identity.ts (missing state.assistantAvatar = null in clear branch) and ui/src/ui/app-render.ts (resolveAssistantAvatarOverride needs to check localStorage).

Comments Outside Diff (1)

  1. ui/src/ui/app-render.ts, line 944 (link)

    P1 assistantAvatarOverride never reflects the localStorage-stored override

    resolveAssistantAvatarOverride reads from configObj (the gateway config snapshot). Because this PR stops writing to the gateway config via config.patch, configObj will never contain the locally-stored avatar. assistantAvatarOverride will therefore always be null for any avatar uploaded through the new flow, which means the button label is permanently "Choose image" instead of "Replace image" after an upload, and the "Clear override" button (rendered only when assistantAvatarOverride is truthy) is never shown — users have no way to clear the override from within Quick Settings.

    The simplest fix is to seed assistantAvatarOverride from localStorage as a fallback:

    const assistantAvatarOverride =
      resolveAssistantAvatarOverride(configObj) ??
      loadLocalAssistantIdentity().avatar ?? null;
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: ui/src/ui/app-render.ts
    Line: 944
    
    Comment:
    **`assistantAvatarOverride` never reflects the localStorage-stored override**
    
    `resolveAssistantAvatarOverride` reads from `configObj` (the gateway config snapshot). Because this PR stops writing to the gateway config via `config.patch`, `configObj` will never contain the locally-stored avatar. `assistantAvatarOverride` will therefore always be `null` for any avatar uploaded through the new flow, which means the button label is permanently "Choose image" instead of "Replace image" after an upload, and the "Clear override" button (rendered only when `assistantAvatarOverride` is truthy) is never shown — users have no way to clear the override from within Quick Settings.
    
    The simplest fix is to seed `assistantAvatarOverride` from localStorage as a fallback:
    
    ```ts
    const assistantAvatarOverride =
      resolveAssistantAvatarOverride(configObj) ??
      loadLocalAssistantIdentity().avatar ?? null;
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/ui/controllers/assistant-identity.ts
Line: 68-72

Comment:
**`state.assistantAvatar` not cleared on override clear**

When `avatar` is `null`, the `else` branch clears `assistantAvatarSource`, `assistantAvatarStatus`, and `assistantAvatarReason` but leaves `state.assistantAvatar` untouched. After clearing, `configAssistantAvatar` in `app-render.ts` is computed as `state.assistantAvatar` (when status is not `"local"` and not missing), so the old data URL is still served to the Quick Settings panel and the `effectiveAssistantAvatar` used for the chat header — the avatar visually persists until a page reload.

```suggestion
  } else {
    state.assistantAvatar = null;
    state.assistantAvatarSource = null;
    state.assistantAvatarStatus = null;
    state.assistantAvatarReason = null;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: ui/src/ui/app-render.ts
Line: 944

Comment:
**`assistantAvatarOverride` never reflects the localStorage-stored override**

`resolveAssistantAvatarOverride` reads from `configObj` (the gateway config snapshot). Because this PR stops writing to the gateway config via `config.patch`, `configObj` will never contain the locally-stored avatar. `assistantAvatarOverride` will therefore always be `null` for any avatar uploaded through the new flow, which means the button label is permanently "Choose image" instead of "Replace image" after an upload, and the "Clear override" button (rendered only when `assistantAvatarOverride` is truthy) is never shown — users have no way to clear the override from within Quick Settings.

The simplest fix is to seed `assistantAvatarOverride` from localStorage as a fallback:

```ts
const assistantAvatarOverride =
  resolveAssistantAvatarOverride(configObj) ??
  loadLocalAssistantIdentity().avatar ?? null;
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(control-ui): persist assistant avata..." | Re-trigger Greptile

Comment on lines +68 to 72
} else {
state.assistantAvatarSource = null;
state.assistantAvatarStatus = null;
state.assistantAvatarReason = null;
}

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.

P1 state.assistantAvatar not cleared on override clear

When avatar is null, the else branch clears assistantAvatarSource, assistantAvatarStatus, and assistantAvatarReason but leaves state.assistantAvatar untouched. After clearing, configAssistantAvatar in app-render.ts is computed as state.assistantAvatar (when status is not "local" and not missing), so the old data URL is still served to the Quick Settings panel and the effectiveAssistantAvatar used for the chat header — the avatar visually persists until a page reload.

Suggested change
} else {
state.assistantAvatarSource = null;
state.assistantAvatarStatus = null;
state.assistantAvatarReason = null;
}
} else {
state.assistantAvatar = null;
state.assistantAvatarSource = null;
state.assistantAvatarStatus = null;
state.assistantAvatarReason = null;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/controllers/assistant-identity.ts
Line: 68-72

Comment:
**`state.assistantAvatar` not cleared on override clear**

When `avatar` is `null`, the `else` branch clears `assistantAvatarSource`, `assistantAvatarStatus`, and `assistantAvatarReason` but leaves `state.assistantAvatar` untouched. After clearing, `configAssistantAvatar` in `app-render.ts` is computed as `state.assistantAvatar` (when status is not `"local"` and not missing), so the old data URL is still served to the Quick Settings panel and the `effectiveAssistantAvatar` used for the chat header — the avatar visually persists until a page reload.

```suggestion
  } else {
    state.assistantAvatar = null;
    state.assistantAvatarSource = null;
    state.assistantAvatarStatus = null;
    state.assistantAvatarReason = null;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

@BunsDev
BunsDev merged commit c65aa1d into main Apr 25, 2026
68 of 71 checks passed
@BunsDev
BunsDev deleted the fix/assistant-avatar-local-override branch April 25, 2026 16:17
vincentkoc added a commit that referenced this pull request Apr 25, 2026
Val Alexander's c65aa1d (#71639) changed assistant avatar uploads
from gateway config persistence to localStorage, mirroring the existing
user-avatar pattern. CHANGELOG covered it but docs/web/control-ui.md
'Personal identity (browser-local)' section only documented the user
identity. Add a paragraph noting the assistant avatar override follows
the same browser-local pattern, while keeping the ui.assistant.avatar
config field reachable for non-UI clients writing the field directly.
ayesha-aziz123 pushed a commit to ayesha-aziz123/openclaw that referenced this pull request Apr 26, 2026
…71639)

* fix(control-ui): rebalance quick settings into stable 3-col bento

Pair Appearance with Automations and let Channels stand alone in the
middle column so all three top-row columns reach similar heights.
Promote Personal to a full-width row with a horizontal body
(identity tiles | emoji + actions) so the avatar block stops fighting
for half-width space. Drops the unused .qs-stack--wide hook.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(control-ui): rebalance Personal card with symmetric User↔Assistant identity pair

Restructure Personal card layout to present User and Assistant as 2 balanced identity cards instead of separate User tile + form controls. Mirrors the visual hierarchy and UI pattern across both identities.

Changes:
- Move User avatar text input into User identity card's .__repair section (mirroring Assistant's structure)
- Inline "Choose image" and "Clear avatar" buttons as flex-wrapped action group
- Remove .qs-personal-body and .qs-personal-form wrapper divs
- Update Personal card's .qs-identity-grid to 2-column layout with balanced spacing
- Responsive collapse to 1-column at ≤760px

Tests:
- config-quick.test.ts updated to expect 2 stacks (no longer wrapping Personal in form)
- config-quick.test.ts validates identity card layout now has symmetric User↔Assistant structure
- All 10 quick settings view tests passing
- All 20 schema regression tests passing

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* chore: ignore .vmux worktree paths

* fix(control-ui): persist assistant avatar override locally instead of via gateway config

Mirrors the user-avatar pattern: assistant avatar uploads now go to
localStorage and overlay the gateway-resolved identity at bootstrap and on
agent.identity.get refreshes. Sidesteps the ui.assistant.avatar zod cap
that rejected uploaded data URLs as 'Too big: expected string to have
<=200 characters', removes one config.patch RPC from the avatar path, and
collapses the upload handler from a 44-line async/loadConfig dance into a
plain synchronous setter.

Also lifts the gateway-side ui.assistant.avatar schema cap from 200 to
2,000,000 to match the user-avatar size budget for non-UI clients writing
the field directly, and adds a content-aware text/image normalizer in
ui/src/ui/assistant-identity.ts so short-text avatars stay short while
data URLs survive round-tripping.

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
ayesha-aziz123 pushed a commit to ayesha-aziz123/openclaw that referenced this pull request Apr 26, 2026
Val Alexander's c65aa1d (openclaw#71639) changed assistant avatar uploads
from gateway config persistence to localStorage, mirroring the existing
user-avatar pattern. CHANGELOG covered it but docs/web/control-ui.md
'Personal identity (browser-local)' section only documented the user
identity. Add a paragraph noting the assistant avatar override follows
the same browser-local pattern, while keeping the ui.assistant.avatar
config field reachable for non-UI clients writing the field directly.
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…71639)

* fix(control-ui): rebalance quick settings into stable 3-col bento

Pair Appearance with Automations and let Channels stand alone in the
middle column so all three top-row columns reach similar heights.
Promote Personal to a full-width row with a horizontal body
(identity tiles | emoji + actions) so the avatar block stops fighting
for half-width space. Drops the unused .qs-stack--wide hook.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(control-ui): rebalance Personal card with symmetric User↔Assistant identity pair

Restructure Personal card layout to present User and Assistant as 2 balanced identity cards instead of separate User tile + form controls. Mirrors the visual hierarchy and UI pattern across both identities.

Changes:
- Move User avatar text input into User identity card's .__repair section (mirroring Assistant's structure)
- Inline "Choose image" and "Clear avatar" buttons as flex-wrapped action group
- Remove .qs-personal-body and .qs-personal-form wrapper divs
- Update Personal card's .qs-identity-grid to 2-column layout with balanced spacing
- Responsive collapse to 1-column at ≤760px

Tests:
- config-quick.test.ts updated to expect 2 stacks (no longer wrapping Personal in form)
- config-quick.test.ts validates identity card layout now has symmetric User↔Assistant structure
- All 10 quick settings view tests passing
- All 20 schema regression tests passing

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* chore: ignore .vmux worktree paths

* fix(control-ui): persist assistant avatar override locally instead of via gateway config

Mirrors the user-avatar pattern: assistant avatar uploads now go to
localStorage and overlay the gateway-resolved identity at bootstrap and on
agent.identity.get refreshes. Sidesteps the ui.assistant.avatar zod cap
that rejected uploaded data URLs as 'Too big: expected string to have
<=200 characters', removes one config.patch RPC from the avatar path, and
collapses the upload handler from a 44-line async/loadConfig dance into a
plain synchronous setter.

Also lifts the gateway-side ui.assistant.avatar schema cap from 200 to
2,000,000 to match the user-avatar size budget for non-UI clients writing
the field directly, and adds a content-aware text/image normalizer in
ui/src/ui/assistant-identity.ts so short-text avatars stay short while
data URLs survive round-tripping.

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
Val Alexander's 9ea8cbb (openclaw#71639) changed assistant avatar uploads
from gateway config persistence to localStorage, mirroring the existing
user-avatar pattern. CHANGELOG covered it but docs/web/control-ui.md
'Personal identity (browser-local)' section only documented the user
identity. Add a paragraph noting the assistant avatar override follows
the same browser-local pattern, while keeping the ui.assistant.avatar
config field reachable for non-UI clients writing the field directly.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…71639)

* fix(control-ui): rebalance quick settings into stable 3-col bento

Pair Appearance with Automations and let Channels stand alone in the
middle column so all three top-row columns reach similar heights.
Promote Personal to a full-width row with a horizontal body
(identity tiles | emoji + actions) so the avatar block stops fighting
for half-width space. Drops the unused .qs-stack--wide hook.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(control-ui): rebalance Personal card with symmetric User↔Assistant identity pair

Restructure Personal card layout to present User and Assistant as 2 balanced identity cards instead of separate User tile + form controls. Mirrors the visual hierarchy and UI pattern across both identities.

Changes:
- Move User avatar text input into User identity card's .__repair section (mirroring Assistant's structure)
- Inline "Choose image" and "Clear avatar" buttons as flex-wrapped action group
- Remove .qs-personal-body and .qs-personal-form wrapper divs
- Update Personal card's .qs-identity-grid to 2-column layout with balanced spacing
- Responsive collapse to 1-column at ≤760px

Tests:
- config-quick.test.ts updated to expect 2 stacks (no longer wrapping Personal in form)
- config-quick.test.ts validates identity card layout now has symmetric User↔Assistant structure
- All 10 quick settings view tests passing
- All 20 schema regression tests passing

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* chore: ignore .vmux worktree paths

* fix(control-ui): persist assistant avatar override locally instead of via gateway config

Mirrors the user-avatar pattern: assistant avatar uploads now go to
localStorage and overlay the gateway-resolved identity at bootstrap and on
agent.identity.get refreshes. Sidesteps the ui.assistant.avatar zod cap
that rejected uploaded data URLs as 'Too big: expected string to have
<=200 characters', removes one config.patch RPC from the avatar path, and
collapses the upload handler from a 44-line async/loadConfig dance into a
plain synchronous setter.

Also lifts the gateway-side ui.assistant.avatar schema cap from 200 to
2,000,000 to match the user-avatar size budget for non-UI clients writing
the field directly, and adds a content-aware text/image normalizer in
ui/src/ui/assistant-identity.ts so short-text avatars stay short while
data URLs survive round-tripping.

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Val Alexander's 8862644 (openclaw#71639) changed assistant avatar uploads
from gateway config persistence to localStorage, mirroring the existing
user-avatar pattern. CHANGELOG covered it but docs/web/control-ui.md
'Personal identity (browser-local)' section only documented the user
identity. Add a paragraph noting the assistant avatar override follows
the same browser-local pattern, while keeping the ui.assistant.avatar
config field reachable for non-UI clients writing the field directly.
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
…71639)

* fix(control-ui): rebalance quick settings into stable 3-col bento

Pair Appearance with Automations and let Channels stand alone in the
middle column so all three top-row columns reach similar heights.
Promote Personal to a full-width row with a horizontal body
(identity tiles | emoji + actions) so the avatar block stops fighting
for half-width space. Drops the unused .qs-stack--wide hook.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(control-ui): rebalance Personal card with symmetric User↔Assistant identity pair

Restructure Personal card layout to present User and Assistant as 2 balanced identity cards instead of separate User tile + form controls. Mirrors the visual hierarchy and UI pattern across both identities.

Changes:
- Move User avatar text input into User identity card's .__repair section (mirroring Assistant's structure)
- Inline "Choose image" and "Clear avatar" buttons as flex-wrapped action group
- Remove .qs-personal-body and .qs-personal-form wrapper divs
- Update Personal card's .qs-identity-grid to 2-column layout with balanced spacing
- Responsive collapse to 1-column at ≤760px

Tests:
- config-quick.test.ts updated to expect 2 stacks (no longer wrapping Personal in form)
- config-quick.test.ts validates identity card layout now has symmetric User↔Assistant structure
- All 10 quick settings view tests passing
- All 20 schema regression tests passing

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* chore: ignore .vmux worktree paths

* fix(control-ui): persist assistant avatar override locally instead of via gateway config

Mirrors the user-avatar pattern: assistant avatar uploads now go to
localStorage and overlay the gateway-resolved identity at bootstrap and on
agent.identity.get refreshes. Sidesteps the ui.assistant.avatar zod cap
that rejected uploaded data URLs as 'Too big: expected string to have
<=200 characters', removes one config.patch RPC from the avatar path, and
collapses the upload handler from a 44-line async/loadConfig dance into a
plain synchronous setter.

Also lifts the gateway-side ui.assistant.avatar schema cap from 200 to
2,000,000 to match the user-avatar size budget for non-UI clients writing
the field directly, and adds a content-aware text/image normalizer in
ui/src/ui/assistant-identity.ts so short-text avatars stay short while
data URLs survive round-tripping.

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
Val Alexander's 8f694b0 (openclaw#71639) changed assistant avatar uploads
from gateway config persistence to localStorage, mirroring the existing
user-avatar pattern. CHANGELOG covered it but docs/web/control-ui.md
'Personal identity (browser-local)' section only documented the user
identity. Add a paragraph noting the assistant avatar override follows
the same browser-local pattern, while keeping the ui.assistant.avatar
config field reachable for non-UI clients writing the field directly.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…71639)

* fix(control-ui): rebalance quick settings into stable 3-col bento

Pair Appearance with Automations and let Channels stand alone in the
middle column so all three top-row columns reach similar heights.
Promote Personal to a full-width row with a horizontal body
(identity tiles | emoji + actions) so the avatar block stops fighting
for half-width space. Drops the unused .qs-stack--wide hook.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(control-ui): rebalance Personal card with symmetric User↔Assistant identity pair

Restructure Personal card layout to present User and Assistant as 2 balanced identity cards instead of separate User tile + form controls. Mirrors the visual hierarchy and UI pattern across both identities.

Changes:
- Move User avatar text input into User identity card's .__repair section (mirroring Assistant's structure)
- Inline "Choose image" and "Clear avatar" buttons as flex-wrapped action group
- Remove .qs-personal-body and .qs-personal-form wrapper divs
- Update Personal card's .qs-identity-grid to 2-column layout with balanced spacing
- Responsive collapse to 1-column at ≤760px

Tests:
- config-quick.test.ts updated to expect 2 stacks (no longer wrapping Personal in form)
- config-quick.test.ts validates identity card layout now has symmetric User↔Assistant structure
- All 10 quick settings view tests passing
- All 20 schema regression tests passing

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* chore: ignore .vmux worktree paths

* fix(control-ui): persist assistant avatar override locally instead of via gateway config

Mirrors the user-avatar pattern: assistant avatar uploads now go to
localStorage and overlay the gateway-resolved identity at bootstrap and on
agent.identity.get refreshes. Sidesteps the ui.assistant.avatar zod cap
that rejected uploaded data URLs as 'Too big: expected string to have
<=200 characters', removes one config.patch RPC from the avatar path, and
collapses the upload handler from a 44-line async/loadConfig dance into a
plain synchronous setter.

Also lifts the gateway-side ui.assistant.avatar schema cap from 200 to
2,000,000 to match the user-avatar size budget for non-UI clients writing
the field directly, and adds a content-aware text/image normalizer in
ui/src/ui/assistant-identity.ts so short-text avatars stay short while
data URLs survive round-tripping.

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
Val Alexander's df4c69a (openclaw#71639) changed assistant avatar uploads
from gateway config persistence to localStorage, mirroring the existing
user-avatar pattern. CHANGELOG covered it but docs/web/control-ui.md
'Personal identity (browser-local)' section only documented the user
identity. Add a paragraph noting the assistant avatar override follows
the same browser-local pattern, while keeping the ui.assistant.avatar
config field reachable for non-UI clients writing the field directly.
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…71639)

* fix(control-ui): rebalance quick settings into stable 3-col bento

Pair Appearance with Automations and let Channels stand alone in the
middle column so all three top-row columns reach similar heights.
Promote Personal to a full-width row with a horizontal body
(identity tiles | emoji + actions) so the avatar block stops fighting
for half-width space. Drops the unused .qs-stack--wide hook.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(control-ui): rebalance Personal card with symmetric User↔Assistant identity pair

Restructure Personal card layout to present User and Assistant as 2 balanced identity cards instead of separate User tile + form controls. Mirrors the visual hierarchy and UI pattern across both identities.

Changes:
- Move User avatar text input into User identity card's .__repair section (mirroring Assistant's structure)
- Inline "Choose image" and "Clear avatar" buttons as flex-wrapped action group
- Remove .qs-personal-body and .qs-personal-form wrapper divs
- Update Personal card's .qs-identity-grid to 2-column layout with balanced spacing
- Responsive collapse to 1-column at ≤760px

Tests:
- config-quick.test.ts updated to expect 2 stacks (no longer wrapping Personal in form)
- config-quick.test.ts validates identity card layout now has symmetric User↔Assistant structure
- All 10 quick settings view tests passing
- All 20 schema regression tests passing

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* chore: ignore .vmux worktree paths

* fix(control-ui): persist assistant avatar override locally instead of via gateway config

Mirrors the user-avatar pattern: assistant avatar uploads now go to
localStorage and overlay the gateway-resolved identity at bootstrap and on
agent.identity.get refreshes. Sidesteps the ui.assistant.avatar zod cap
that rejected uploaded data URLs as 'Too big: expected string to have
<=200 characters', removes one config.patch RPC from the avatar path, and
collapses the upload handler from a 44-line async/loadConfig dance into a
plain synchronous setter.

Also lifts the gateway-side ui.assistant.avatar schema cap from 200 to
2,000,000 to match the user-avatar size budget for non-UI clients writing
the field directly, and adds a content-aware text/image normalizer in
ui/src/ui/assistant-identity.ts so short-text avatars stay short while
data URLs survive round-tripping.

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
Val Alexander's ceaa14c (openclaw#71639) changed assistant avatar uploads
from gateway config persistence to localStorage, mirroring the existing
user-avatar pattern. CHANGELOG covered it but docs/web/control-ui.md
'Personal identity (browser-local)' section only documented the user
identity. Add a paragraph noting the assistant avatar override follows
the same browser-local pattern, while keeping the ui.assistant.avatar
config field reachable for non-UI clients writing the field directly.
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…71639)

* fix(control-ui): rebalance quick settings into stable 3-col bento

Pair Appearance with Automations and let Channels stand alone in the
middle column so all three top-row columns reach similar heights.
Promote Personal to a full-width row with a horizontal body
(identity tiles | emoji + actions) so the avatar block stops fighting
for half-width space. Drops the unused .qs-stack--wide hook.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

* refactor(control-ui): rebalance Personal card with symmetric User↔Assistant identity pair

Restructure Personal card layout to present User and Assistant as 2 balanced identity cards instead of separate User tile + form controls. Mirrors the visual hierarchy and UI pattern across both identities.

Changes:
- Move User avatar text input into User identity card's .__repair section (mirroring Assistant's structure)
- Inline "Choose image" and "Clear avatar" buttons as flex-wrapped action group
- Remove .qs-personal-body and .qs-personal-form wrapper divs
- Update Personal card's .qs-identity-grid to 2-column layout with balanced spacing
- Responsive collapse to 1-column at ≤760px

Tests:
- config-quick.test.ts updated to expect 2 stacks (no longer wrapping Personal in form)
- config-quick.test.ts validates identity card layout now has symmetric User↔Assistant structure
- All 10 quick settings view tests passing
- All 20 schema regression tests passing

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

* chore: ignore .vmux worktree paths

* fix(control-ui): persist assistant avatar override locally instead of via gateway config

Mirrors the user-avatar pattern: assistant avatar uploads now go to
localStorage and overlay the gateway-resolved identity at bootstrap and on
agent.identity.get refreshes. Sidesteps the ui.assistant.avatar zod cap
that rejected uploaded data URLs as 'Too big: expected string to have
<=200 characters', removes one config.patch RPC from the avatar path, and
collapses the upload handler from a 44-line async/loadConfig dance into a
plain synchronous setter.

Also lifts the gateway-side ui.assistant.avatar schema cap from 200 to
2,000,000 to match the user-avatar size budget for non-UI clients writing
the field directly, and adds a content-aware text/image normalizer in
ui/src/ui/assistant-identity.ts so short-text avatars stay short while
data URLs survive round-tripping.

---------

Co-authored-by: Claude Opus 4.7 <[email protected]>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
Val Alexander's 239a436 (openclaw#71639) changed assistant avatar uploads
from gateway config persistence to localStorage, mirroring the existing
user-avatar pattern. CHANGELOG covered it but docs/web/control-ui.md
'Personal identity (browser-local)' section only documented the user
identity. Add a paragraph noting the assistant avatar override follows
the same browser-local pattern, while keeping the ui.assistant.avatar
config field reachable for non-UI clients writing the field directly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant