Skip to content

fix(ui): keep agent avatar initials on grapheme clusters#110837

Open
nocodet888-arch wants to merge 1 commit into
openclaw:mainfrom
nocodet888-arch:fix/ui-avatar-grapheme-initial
Open

fix(ui): keep agent avatar initials on grapheme clusters#110837
nocodet888-arch wants to merge 1 commit into
openclaw:mainfrom
nocodet888-arch:fix/ui-avatar-grapheme-initial

Conversation

@nocodet888-arch

@nocodet888-arch nocodet888-arch commented Jul 18, 2026

Copy link
Copy Markdown

What Problem This Solves

Control UI agent avatar fallbacks used .slice(0, 1) on the agent name/id.
Names starting with a flag emoji (🇺🇸Team) or ZWJ sequence (👨‍💻Dev) were
cut mid-cluster and rendered as replacement characters / incomplete glyphs.
Engines without Intl.Segmenter also dropped safe non-ASCII BMP letters
(e.g. 東京, Émile) to ? when only ASCII [A-Za-z0-9] was accepted.

Why This Change Was Made

Add resolveFallbackAvatarInitial using Intl.Segmenter grapheme granularity
and use it in the three agent avatar fallback call sites. Keep the prior
locale-independent toUpperCase() casing contract (not toLocaleUpperCase()).

When Intl.Segmenter is unavailable, take the first code point only when it
is a single BMP Unicode letter (\p{L}) or decimal digit (\p{Nd}). Do not
use UTF-16 .slice or non-BMP first-code-point emoji (flags / ZWJ) — those stay
? so incomplete glyphs are never shown.

User Impact

Before: Agent names beginning with multi-code-point emoji produced broken
avatar initials; non-ASCII letter names became ? without Segmenter.

After: Fallback avatars keep a complete grapheme (flag / ZWJ emoji / letter)
with Segmenter. Without Segmenter, safe BMP letters/digits still render
(東京, ÉmileÉ) while flag/ZWJ-leading names show ?.

Evidence

  • Changed: ui/src/lib/agents/display.ts, ui/src/lib/agents/display.test.ts,
    ui/src/components/app-sidebar.ts,
    ui/src/components/app-sidebar-agent-menu.ts,
    ui/src/pages/agents/panels-overview.ts,
    ui/src/test-helpers/app-sidebar-cases/agent-menu.ts,
    ui/src/e2e/avatar-initials.e2e.test.ts
  • Production path: sidebar agent card / agent menu / agents overview identity
    editor → resolveFallbackAvatarInitial(label) when no text avatar is configured
  • Compatibility: ASCII initials still uppercased via toUpperCase(); empty
    labels become ?; Segmenter-missing path keeps BMP \p{L}/\p{Nd} and
    refuses flag/ZWJ clusters (?) rather than splitting them
  • Visual proof: Playwright Control UI e2e screenshots under
    .artifacts/control-ui-e2e/avatar-initials/ (not committed):
    flag-avatar.png, zwj-avatar.png, tokyo-avatar.png

Real behavior proof

Behavior or issue addressed

Mounted Control UI sidebar renders 🇺🇸 / 👨‍💻 / in the agent-chip avatar
slot for flag-, ZWJ-, and Japanese-leading agent names. Without Segmenter,
flag/ZWJ labels become ? while BMP letters (東京, Émile) still resolve.

Canonical reachability path

Control UI agent chip / menu / overview →
resolveAgentTextAvatar(...) ?? resolveFallbackAvatarInitial(label)
.sidebar-agent-card__avatar-text / .sidebar-agent-section__avatar

Shared helper / provider constraint check

Same Segmenter primary path as ui/src/pages/plugins/presentation.ts
takeGraphemes, but avatar fallback refuses non-BMP / non-letter first code
points without Segmenter (no Array.from emoji halves); no new config.

Real environment tested

Local trusted source checkout at PR head; jsdom Control UI component mount via
mountSidebar; Playwright Chromium mocked Control UI e2e via
startControlUiE2eServer + installMockGateway; Node via scripts/run-vitest.mjs.

Exact steps or command run after this patch

node scripts/run-vitest.mjs ui/src/lib/agents/display.test.ts \
  -t "resolveFallbackAvatarInitial" --reporter=verbose
node scripts/run-vitest.mjs ui/src/components/app-sidebar.test.ts \
  -t "complete flag and ZWJ" --reporter=verbose
node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts \
  --configLoader runner ui/src/e2e/avatar-initials.e2e.test.ts --reporter=verbose

Evidence after fix

Helper + Segmenter-missing BMP-safe fallback:

stdout | resolveFallbackAvatarInitial > keeps flag and ZWJ emoji clusters intact
avatar-initial proof: {"flag":"🇺🇸","zwj":"👨‍💻","ascii":"A","tokyo":"東","accented":"É"}

stdout | resolveFallbackAvatarInitial > preserves safe BMP letters/digits without Segmenter; refuses flag/ZWJ splits
avatar-initial Segmenter-fallback proof: {"ascii":"A","tokyo":"東","accented":"É","digit":"9","flag":"?","zwj":"?"}

 ✓ resolveFallbackAvatarInitial > keeps flag and ZWJ emoji clusters intact
 ✓ resolveFallbackAvatarInitial > keeps locale-independent ASCII casing (Turkish i)
 ✓ resolveFallbackAvatarInitial > preserves safe BMP letters/digits without Segmenter; refuses flag/ZWJ splits

 Test Files  1 passed (1)
      Tests  3 passed | 17 skipped (20)

Rendered Control UI avatar slots (production Lit templates via mountSidebar):

stdout | AppSidebar agent chip > renders complete flag and ZWJ graphemes in the agent chip avatar slot
sidebar avatar-slot proof: {"chip":"🇺🇸","flagMenu":"🇺🇸","zwjMenu":"👨‍💻"}

 ✓ AppSidebar agent chip > renders complete flag and ZWJ graphemes in the agent chip avatar slot

 Test Files  1 passed (1)
      Tests  1 passed | 141 skipped (142)

Playwright Control UI e2e (DOM initials + screenshots, not committed):

stdout | Control UI avatar grapheme initials E2E > renders complete flag, ZWJ, and Unicode letter initials in the agent chip
control-ui avatar-initials e2e proof: {"flag-avatar.png":"🇺🇸","zwj-avatar.png":"👨‍💻","tokyo-avatar.png":"東"}
control-ui avatar-initials artifact dir: /private/tmp/oc-wt-110837/.artifacts/control-ui-e2e/avatar-initials

 ✓ |ui-e2e| ui/src/e2e/avatar-initials.e2e.test.ts > Control UI avatar grapheme initials E2E > renders complete flag, ZWJ, and Unicode letter initials in the agent chip 9955ms

 Test Files  1 passed (1)
      Tests  1 passed (1)

Local screenshot paths (gitignored, not committed):

  • .artifacts/control-ui-e2e/avatar-initials/flag-avatar.png — chip shows 🇺🇸 for 🇺🇸Team
  • .artifacts/control-ui-e2e/avatar-initials/zwj-avatar.png — chip shows 👨‍💻 for 👨‍💻Dev
  • .artifacts/control-ui-e2e/avatar-initials/tokyo-avatar.png — chip shows for 東京

Observed result after fix

The live sidebar agent-chip avatar slots render complete flag and ZWJ graphemes
and ordinary Unicode letter initials instead of UTF-16-sliced fragments, without
changing the prior toUpperCase() casing contract. The Segmenter-unavailable
path keeps safe BMP letters/digits and still refuses flag/ZWJ halves.

AI-assisted: yes. I reviewed and understand the change.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 19, 2026, 11:15 AM ET / 15:15 UTC.

Summary
Adds a grapheme-aware avatar-initial helper, replaces three UTF-16-sliced fallback call sites, and adds unit, component, and mocked Playwright coverage for emoji and Unicode initials.

PR surface: Source +105, Tests +150. Total +255 across 7 files.

Reproducibility: no. independent high-confidence current-main reproduction was available in this review. The changed fallback call sites provide a clear source-level path for emoji-leading labels to be truncated by UTF-16 slicing.

Review metrics: none identified.

Stored data model
Persistent data-model change detected: serialized state: ui/src/lib/agents/display.test.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🐚 platinum hermit
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Add a redacted screenshot, recording, terminal capture, or linked artifact from an after-fix Control UI run against a real Gateway path.
  • Update the PR body with that evidence; if automatic review does not resume, ask a maintainer to comment @clawsweeper re-review.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR provides useful unit, mounted-component, and mocked Playwright output, but no inspectable redacted screenshot, recording, live output, or artifact from a real Control UI setup; add one before merge and redact private details. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] Merging without inspectable real-runtime evidence leaves the user-visible Control UI result unverified; mocked Gateway and Playwright output alone do not demonstrate the changed path in a real setup.

Maintainer options:

  1. Decide the mitigation before merge
    Retain the shared fallback approach if it still matches current main, and add redacted after-fix evidence from an actual Control UI session showing the three avatar outputs before merge.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] The remaining merge gate is contributor-supplied real behavior proof, not a concrete repair task for an automated fix lane.

Security
Cleared: The patch changes local Control UI display logic and test scaffolding without adding dependencies, credentials, permissions, downloads, or new code-execution paths.

Review details

Best possible solution:

Retain the shared fallback approach if it still matches current main, and add redacted after-fix evidence from an actual Control UI session showing the three avatar outputs before merge.

Do we have a high-confidence way to reproduce the issue?

No independent high-confidence current-main reproduction was available in this review. The changed fallback call sites provide a clear source-level path for emoji-leading labels to be truncated by UTF-16 slicing.

Is this the best way to solve the issue?

Yes, conditionally: one shared fallback helper is the maintainable way to keep the three UI paths consistent, but it needs current-main comparison and real behavior proof before it is accepted as the best landed fix.

AGENTS.md: unclear because the file could not be read completely.

Codex review notes: model internal, reasoning high; reviewed against 223235044a29.

Label changes

Label justifications:

  • P3: This is a contained fallback-avatar rendering defect with limited user impact and no evidence of message, session, auth, or availability impact.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR provides useful unit, mounted-component, and mocked Playwright output, but no inspectable redacted screenshot, recording, live output, or artifact from a real Control UI setup; add one before merge and redact private details. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +105, Tests +150. Total +255 across 7 files.

View PR surface stats
Area Files Added Removed Net
Source 5 110 5 +105
Tests 2 152 2 +150
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 7 262 7 +255

What I checked:

  • Shared fallback implementation: The branch centralizes fallback-initial selection with Intl.Segmenter and a conservative no-Segmenter BMP letter/digit path, then routes the sidebar, agent menu, and overview through that helper. (ui/src/lib/agents/display.ts:320, adf983f9583f)
  • Regression coverage: The branch adds cases for flags, ZWJ emoji, Unicode letters, digits, whitespace, and a Segmenter-unavailable fallback, which addresses the earlier fallback compatibility concern in the supplied review history. (ui/src/lib/agents/display.test.ts:72, adf983f9583f)
  • Proof remains mock-based: The submitted E2E test starts a Control UI test server and installs a mocked Gateway; the claimed screenshots are explicitly local and uncommitted, with no inspectable media or live-runtime artifact supplied in the PR context. (ui/src/e2e/avatar-initials.e2e.test.ts:1, adf983f9583f)
  • Related CI work is distinct: The referenced merged PR concerns CI mergeability recovery for this PR, not the Control UI grapheme-rendering behavior, so it does not supersede this patch. (0f5d030cb10c)

Likely related people:

  • steipete: The supplied timeline identifies this contributor on the only linked merged work affecting this PR's review/CI path; current UI-file history could not be inspected in the read-only sandbox, so this is only a low-confidence escalation contact rather than a code-ownership attribution. (role: related-work author; confidence: low; commits: 0f5d030cb10c; files: test/scripts/pr-ci-sweeper.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (8 earlier review cycles)
  • reviewed 2026-07-18T17:04:55.662Z sha 87db9be :: needs real behavior proof before merge. :: [P2] Preserve locale-independent avatar casing
  • reviewed 2026-07-18T17:39:54.650Z sha 501983c :: needs real behavior proof before merge. :: [P2] Make the non-Segmenter fallback grapheme-aware
  • reviewed 2026-07-18T18:22:13.567Z sha 501983c :: needs real behavior proof before merge. :: [P2] Make the non-Segmenter fallback grapheme-aware
  • reviewed 2026-07-18T22:26:19.572Z sha 501983c :: needs real behavior proof before merge. :: [P2] Make the fallback preserve grapheme clusters
  • reviewed 2026-07-19T01:00:26.223Z sha 501983c :: needs real behavior proof before merge. :: [P2] Preserve grapheme clusters in the fallback
  • reviewed 2026-07-19T11:22:31.370Z sha dc6fc3f :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-19T12:05:25.860Z sha dc6fc3f :: needs real behavior proof before merge. :: [P2] Preserve non-ASCII initials without Segmenter
  • reviewed 2026-07-19T14:32:49.145Z sha adf983f :: needs real behavior proof before merge. :: none

@nocodet888-arch

Copy link
Copy Markdown
Author

@clawsweeper re-review

Switched the helper to toUpperCase() (locale-independent casing) and added a Control UI sidebar mount proof that asserts the production avatar slots render complete 🇺🇸 / 👨‍💻 graphemes. PR body refreshed with that evidence.

@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. label Jul 18, 2026
@openclaw-barnacle openclaw-barnacle Bot reopened this Jul 18, 2026
@openclaw-barnacle openclaw-barnacle Bot reopened this Jul 19, 2026
@nocodet888-arch
nocodet888-arch force-pushed the fix/ui-avatar-grapheme-initial branch from 501983c to dc6fc3f Compare July 19, 2026 11:18
@nocodet888-arch

Copy link
Copy Markdown
Author

@clawsweeper re-review

Rebased onto latest main, squashed to one commit, and addressed the P2 Segmenter fallback finding:

  • Kept Intl.Segmenter as the primary grapheme path
  • Removed the Array.from fallback that still split flags/ZWJ
  • Segmenter-missing path now keeps ASCII initials only and returns ? for emoji-leading labels
  • Added a forced Segmenter-unavailable regression test plus sidebar mount proof for 🇺🇸 / 👨‍💻

Updated PR body Evidence + Real behavior proof with verbatim vitest output.

@clawsweeper

clawsweeper Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. label Jul 19, 2026
@nocodet888-arch
nocodet888-arch force-pushed the fix/ui-avatar-grapheme-initial branch from dc6fc3f to adf983f Compare July 19, 2026 14:28
@nocodet888-arch

Copy link
Copy Markdown
Author

@clawsweeper re-review

Addressed P1s:

  1. No-Segmenter BMP-safe initials — without Intl.Segmenter, first code point is kept when it is a single BMP \p{L} / \p{Nd} (東京, ÉmileÉ, 9lives9). Flag / ZWJ / non-BMP starters still return ? (no half-glyph splits). Regression coverage in ui/src/lib/agents/display.test.ts.

  2. Control UI visual proof (Playwright, mocked Gateway) — new ui/src/e2e/avatar-initials.e2e.test.ts asserts .sidebar-agent-card__avatar-text for 🇺🇸Team / 👨‍💻Dev / 東京 and writes gitignored screenshots:

control-ui avatar-initials e2e proof: {"flag-avatar.png":"🇺🇸","zwj-avatar.png":"👨‍💻","tokyo-avatar.png":"東"}
artifact dir: .artifacts/control-ui-e2e/avatar-initials/
  flag-avatar.png  → avatar chip shows 🇺🇸
  zwj-avatar.png   → avatar chip shows 👨‍💻
  tokyo-avatar.png → avatar chip shows 東

Screenshots are local/gitignored (not committed; gh gist rejects binary PNG upload). DOM assertion log above is the redacted after-fix Control UI proof; crabbox artifact hosting was unavailable in this environment.

Head: adf983f9583 — single commit on upstream/main.

@clawsweeper

clawsweeper Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. label Jul 19, 2026
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 P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: M status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant