google-gemini / google-gemini/gemini-cli
ExpandableText can drop an emoji at a truncation boundary
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
### What happened?
`ExpandableText` can drop a complete supplementary character when it
collapses a label. The no-match path uses JavaScript UTF-16 length and slice
indexes as if they were Unicode character indexes. If the limit lands on the
high surrogate of an emoji, Ink receives an unpaired surrogate and the TUI
silently omits the emoji.
For example, with label `aaaa😀tail` and `maxWidth 5`, the component renders
`aaaa...` instead of retaining the complete 😀 at the boundary. ASCII
truncation and a centered-match case both pass.
From this report directory, run:
```sh
bash attachments/repro.sh
```
The script imports the exact `ExpandableText.tsx` component and renders it
with the installed Ink renderer. Three consecutive runs produced the same
compact result:
```text
PASS ascii
FAIL no-match high-surrogate expected="aaaa😀" frame="aaaa..."
PASS centered-match
FAIL production boundary ...
checked=4 failures=2
```
The complete frame output and run checksum are in
[attachments/evidence.log](attachments/evidence.log).
### What did you expect to happen?
Truncation should stop at a valid Unicode boundary and should not discard a
complete supplementary character merely because its UTF-16 representation uses
two code units. At minimum the component should never pass an unpaired
surrogate to Ink; the retained prefix should contain `aaaa😀` before the
component's existing truncation marker.
### Client information
Client Information
Run `gemini` to enter the interactive CLI, then run the `/about` command.
```console
This is a component-level reproduction and does not make a Gemini API request,
open a login session, or use an account. The matching source build reports
`0.61.0-nightly.20260908.gc647533d6` from `--version`. A redacted runtime
record is available at [attachments/environment.txt](attachments/environment.txt).
The interactive `/about` command was not available in this no-auth run: the
isolated CLI exits before opening the TUI when no authentication method is
configured. No account-dependent output is being substituted for it.
Package under test:
`@google/gemini-cli 0.61.0-nightly.20260908.gc647533d6`
Audited source revision:
`9c1b0a610534d6f8120964cf2672c07807d8fc90`
```
### Login information
Not applicable. No provider, API key, Google account, or network request is
used by the reproducer.
### Anything else we need to know?
The no-match truncation branch checks `label.length` and then uses
`label.slice(0, maxWidth)` before appending the marker. The source-level
assertion proves that `maxWidth 5` cuts a surrogate pair. The component is
used by both the suggestions popup and rewind viewer, so this is a live CLI
component rather than an unused helper.
The audited revision is the public Gemini CLI `main` revision checked on
2026-09-12. The relevant files were unchanged at that check:
[ExpandableText.tsx](https://github.com/google-gemini/gemini-cli/blob/9c1b0a610534d6f8120964cf2672c07807d8fc90/packages/cli/src/ui/components/shared/ExpandableText.tsx#L23-L58)
[ExpandableText.tsx](https://github.com/google-gemini/gemini-cli/blob/9c1b0a610534d6f8120964cf2672c07807d8fc90/packages/cli/src/ui/components/shared/ExpandableText.tsx#L68-L115)
[SuggestionsDisplay.tsx](https://github.com/google-gemini/gemini-cli/blob/9c1b0a610534d6f8120964cf2672c07807d8fc90/packages/cli/src/ui/components/SuggestionsDisplay.tsx#L98-L105)
and
[RewindViewer.tsx](https://github.com/google-gemini/gemini-cli/blob/9c1b0a610534d6f8120964cf2672c07807d8fc90/packages/cli/src/ui/components/RewindViewer.tsx#L287-L297)
are the relevant call sites.
I searched the existing Gemini CLI issues and did not find this
`ExpandableText` UTF-16 boundary case. This is separate from
[PR #12217](https://github.com/google-gemini/gemini-cli/pull/12217), which
changes `TextBuffer` behavior and does not cover `ExpandableText`'s slicing
path.
The report artifacts are:
- [repro.sh](attachments/repro.sh)
- [evidence.log](attachments/evidence.log)
- [environment.txt](attachments/environment.txt)
- [client-info.txt](attachments/client-info.txt)
- [attachments.zip](attachments.zip)
[attachments.zip](https://github.com/user-attachments/files/32143967/attachments.zip)
Contributor guide
Research direction
Start in packages/cli/src/ui/components/shared/ExpandableText.tsx and run attachments/repro.sh to observe the no-match high-surrogate failure. Review the truncation branch and its existing marker behavior; done means the reproduction retains complete supplementary characters, never passes an unpaired surrogate to Ink, and all four checks pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 86/100