Skip to content

feat(mascot): accept PNG/GIF/JPEG/WebP custom avatars with file upload - #5420

Merged
M3gA-Mind merged 4 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/5360-image-custom-avatars
Aug 6, 2026
Merged

feat(mascot): accept PNG/GIF/JPEG/WebP custom avatars with file upload#5420
M3gA-Mind merged 4 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/5360-image-custom-avatars

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Custom avatars accepted only .gif URLs. Widened validation to any safe raster image — PNG/JPEG/WebP/GIF/BMP — for https/loopback-http/file:///local-path URLs, and added base64 data:image/* as an accepted source. SVG stays rejected (an SVG can carry inline scripts, and the render path is a plain <img>).
  • Added an Upload image button to the Mascot settings panel. The picked file is inlined as a data URL onto the existing customMascotGifUrl field, so the render path (CustomGifMascot<img>) is unchanged and GIF animation is preserved for free.
  • Upload is gated on the shared MIME allowlist and a 1.5 MB source-file cap before dispatch. That cap matters: uploaded avatars persist into the localStorage-backed mascot slice, and userScopedStorage.setItem swallows QuotaExceededError — an oversize write would silently drop the entire slice (colour, voice, selection). A reducer-boundary length backstop (~2.2 MB, base64's ~4/3 inflation) catches a hand-pasted or tampered data URL that skipped the UI check.
  • When the stored avatar is an uploaded data URL, the URL text box stays blank rather than dumping ~2 MB of base64 into an input, and Save is disabled on an empty box so it can't be read as "clear". Reset still clears.
  • Relabelled the setting "Custom GIF avatar" → "Custom image avatar" and added the three new upload error strings across all 15 locales.

Test plan

  • Typecheck passes (pnpm compile)
  • Lint passes (eslint, changed files)
  • Format passes (prettier --check)
  • Unit tests pass — 96 in mascotSlice/MascotPanel/CustomGifMascot, plus 71 i18n coverage tests confirming no locale is missing the new keys

Closes #5360

Summary by CodeRabbit

  • New Features

    • Upload custom mascot avatars in PNG, GIF, JPEG, or WebP formats.
    • Use HTTPS, local, and file:// image sources for mascot avatars.
    • Added localized upload controls, guidance, and validation messages.
  • Bug Fixes

    • Prevented invalid, oversized, or unreadable images from being saved.
    • Preserved uploaded avatars when the URL field is blank.
    • Improved support for valid raster image formats while continuing to reject unsafe sources.

- Widen custom avatar validation from `.gif`-only to any safe raster image
  (PNG/JPEG/WebP/GIF/BMP) for URLs, plus base64 `data:image/*` sources for
  uploads. SVG stays rejected (inline-script risk).
- Add an "Upload image" button in the Mascot settings panel that inlines a
  local file as a data URL, gated on MIME allowlist and a 1.5 MB size cap so
  an oversize blob can't blow the localStorage quota and drop the whole
  mascot slice.
- Relabel the setting to "Custom image avatar" across all 15 locales.

Closes tinyhumansai#5360
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7ebe6126-3c5b-4221-9e5b-2cc6100e5ddc

📥 Commits

Reviewing files that changed from the base of the PR and between 7837aa3 and 5556b0a.

📒 Files selected for processing (18)
  • app/src/components/settings/panels/MascotPanel.tsx
  • app/src/components/settings/panels/__tests__/MascotPanel.test.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/store/__tests__/mascotSlice.test.ts
  • app/src/store/mascotSlice.ts
📝 Walkthrough

Walkthrough

Mascot settings now accept raster image URLs and local PNG, GIF, JPEG, and WebP uploads. Files are validated for type and size, converted to data URIs, persisted as custom avatar URLs, and covered by localized errors and tests.

Changes

Custom mascot avatar support

Layer / File(s) Summary
Raster avatar validation and limits
app/src/store/mascotSlice.ts, app/src/store/__tests__/mascotSlice.test.ts
Custom avatars now accept bounded raster image paths and base64 data URLs. Tests cover supported, unsafe, malformed, SVG, and oversized sources.
Local avatar upload flow
app/src/components/settings/panels/MascotPanel.tsx, app/src/components/settings/panels/__tests__/MascotPanel.test.tsx
The panel adds image selection, MIME and size checks, data-URI conversion, preview handling, reset behavior, and upload tests.
Localized avatar controls and errors
app/src/lib/i18n/{ar,bn,de,en,es,fr,hi,id,it,ko,pl,pt,ru,zh-CN}.ts
Translations describe supported image sources, upload controls, size limits, unsupported types, and read failures.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MascotPanel
  participant fileToDataUri
  participant mascotSlice
  User->>MascotPanel: Select image file
  MascotPanel->>MascotPanel: Validate MIME type and size
  MascotPanel->>fileToDataUri: Convert valid file
  fileToDataUri-->>MascotPanel: Return data URI
  MascotPanel->>mascotSlice: Store custom mascot URL
Loading

Possibly related PRs

Suggested labels: feature

Poem

A rabbit brings a PNG so bright,
A GIF that loops through day and night.
The mascot checks its type and size,
Then stores the image as a prize.
Hop, hop—the avatar flies!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes support for raster custom avatars and local file uploads.
Linked Issues check ✅ Passed The changes support PNG and GIF uploads, preserve GIF animation, and retain the existing Rive path as required by issue [#5360].
Out of Scope Changes check ✅ Passed The added formats, URL sources, translations, validation, tests, and upload handling all support the custom-avatar objective in [#5360].

Comment @coderabbitai help to get the list of available commands.

@YellowSnnowmann
YellowSnnowmann marked this pull request as ready for review August 6, 2026 10:03
@YellowSnnowmann
YellowSnnowmann requested a review from a team August 6, 2026 10:03

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label Aug 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c818c1eb4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/src/components/settings/panels/MascotPanel.tsx

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 19

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/settings/panels/MascotPanel.tsx`:
- Around line 203-207: Add an analyticsId to the upload Button associated with
the custom mascot GIF flow, then call trackAnalyticsEvent immediately after
dispatch(setCustomMascotGifUrl(dataUri)) for successful uploads. Include only
privacy-safe, stable dimensions such as upload outcome or file category, and do
not send file contents, names, or other identifying data.
- Line 645: Add BMP support consistently across local avatar uploads: in
app/src/components/settings/panels/MascotPanel.tsx lines 645-645, update the
upload control’s accept value to include image/bmp; in
app/src/components/settings/panels/__tests__/MascotPanel.test.tsx lines 320-358,
add a BMP upload test verifying conversion and persistence; in
app/src/lib/i18n/ar.ts lines 5720-5729, update the supported-image guidance and
validation text to mention BMP.
- Around line 208-210: Remove the error object from the console.debug call in
the FileReader failure catch within the custom GIF upload flow, keeping only a
fixed non-identifying failure message or event before setting customGifError.
Preserve the existing user-facing error handling.

In `@app/src/lib/i18n/bn.ts`:
- Line 5853: Update the Bengali avatar URL guidance and upload type error
messages near the shown translation entry so both enumerate every accepted
source and format: HTTPS, loopback HTTP, file://, local paths, base64
data:image/* sources, and PNG, GIF, JPEG, WebP, and BMP. Keep the wording
consistent across both messages.

In `@app/src/lib/i18n/de.ts`:
- Around line 6014-6020: Update the German strings for the avatar URL guidance
and invalid file type, identified by the
settings.mascot.customGifDescription/customGifInvalidType keys, to mention
HTTPS, loopback HTTP, file://, local paths, data:image/* sources, and PNG, GIF,
JPEG, WebP, and BMP formats. Preserve the existing German wording style while
ensuring both messages list the complete supported sources and formats.

In `@app/src/lib/i18n/en.ts`:
- Line 6452: Update the settings.persona.appearanceDesc translation to replace
the GIF-specific “custom GIF avatar” wording with the consistent “custom image
avatar” terminology, matching settings.mascot.customGifHeading.
- Around line 6451-6457: Update the avatar URL guidance and customGifInvalidType
message to list all supported sources—HTTPS, loopback HTTP, file://, local URLs,
and data:image/*—and all supported formats, including PNG, GIF, JPEG, WebP, and
BMP. Remove the HTTPS-only wording while keeping SVG excluded from both
messages.

In `@app/src/lib/i18n/es.ts`:
- Line 5962: Update the Spanish avatar guidance strings at the shown message and
its related entries to mention every accepted source and format: HTTPS, loopback
HTTP, file:// and local paths, base64 data:image/* URLs, and PNG, GIF, JPEG,
WebP, and BMP. Keep the wording consistent across all applicable translations
and aligned with the avatar input contract.

In `@app/src/lib/i18n/fr.ts`:
- Around line 5993-5999: Update the French strings for the custom mascot image
guidance, specifically settings.mascot.customGifHeading and
settings.mascot.customGifInvalidType, to include loopback HTTP and data:image/*
among supported sources and BMP among supported formats, while preserving the
existing French wording and supported PNG, GIF, JPEG, and WebP entries.

In `@app/src/lib/i18n/hi.ts`:
- Around line 5850-5856: Update the Hindi mascot image guidance strings,
including settings.mascot.customGifError and the adjacent custom URL/upload
validation message, to list every supported source: HTTPS, loopback HTTP,
file://, local paths, and data:image/*; list PNG, JPEG, WebP, GIF, and BMP
formats consistently.

In `@app/src/lib/i18n/id.ts`:
- Around line 5879-5888: Update the Indonesian strings
settings.mascot.customGifError and settings.mascot.customGifInvalidType to
include BMP as a supported image format, matching the formats accepted by
isCustomMascotGifUrl; preserve the existing guidance for PNG, GIF, JPEG, and
WebP.

In `@app/src/lib/i18n/it.ts`:
- Around line 5949-5958: Update the Italian messages
settings.mascot.customGifError and settings.mascot.customGifInvalidType to
reflect the full supported image contract: HTTPS, loopback HTTP, file://, local
paths, data:image/*, and PNG, JPEG, WebP, GIF, and BMP formats.

In `@app/src/lib/i18n/ko.ts`:
- Around line 5783-5789: Update the Korean mascot custom image help and
invalid-type messages associated with settings.mascot.customGifHeading and
settings.mascot.customGifInvalidType to include BMP alongside PNG, GIF, JPEG,
and WebP. Ensure the manual-entry guidance also enumerates the
validator-supported loopback HTTP, local path, and data:image/* sources when
applicable, keeping both messages consistent with the complete accepted source
set.

In `@app/src/lib/i18n/pl.ts`:
- Line 5940: Update the translation string near the image-source validation
error to list every URL-validator-supported source: loopback HTTP, HTTPS,
file://, local images, and data:image/*, including BMP where applicable. Keep
the upload-specific format list separate if BMP is not accepted for uploads, and
align the wording with the validator’s actual accepted formats.

In `@app/src/lib/i18n/pt.ts`:
- Around line 5943-5951: Update the Portuguese strings for
settings.mascot.customGifInvalidType and the preceding image URL/upload guidance
to include BMP among the supported formats, keeping the existing PNG, GIF, JPEG,
and WebP formats unchanged.

In `@app/src/lib/i18n/ru.ts`:
- Around line 5910-5918: Update the Russian validation strings near
settings.mascot.customGifInvalidType and the preceding URL guidance to reflect
the complete supported image matrix: include BMP, loopback HTTP, local paths,
and raster data URLs alongside PNG, GIF, JPEG, and WebP. Keep the wording
consistent across the input guidance and invalid-type message.

In `@app/src/lib/i18n/zh-CN.ts`:
- Line 5543: Update the related guidance strings at the shown entries to
accurately list every validator-supported source: HTTPS, loopback HTTP, file://,
local paths, base64 data:image/* sources, and uploaded files. Include all
accepted image formats—PNG, GIF, JPEG, WebP, and BMP—and describe local paths as
paths rather than links.

In `@app/src/store/mascotSlice.ts`:
- Around line 125-143: The isCustomMascotGifUrl validation flow lacks
privacy-safe diagnostics for its outcomes. Add verbose, grep-friendly
[mascot-avatar] logs at entry, each validation branch/rejection, and success,
recording only a source category and input length; never include the URL, local
path, data URL, or decoded content.
- Around line 102-115: Update isCustomMascotAvatarDataUrl and
CUSTOM_MASCOT_AVATAR_DATA_URL_RE in app/src/store/mascotSlice.ts (lines 102-115)
to require complete base64 quartets with valid optional padding, rejecting
malformed payloads such as A=. Extend the malformed-padding coverage in
app/src/store/__tests__/mascotSlice.test.ts (lines 247-256) and assert both
validation and reducer rejection for those inputs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d3fcc13-5eef-46e5-b5bd-4ccb6eaf4a5d

📥 Commits

Reviewing files that changed from the base of the PR and between e29bfc6 and 9c818c1.

📒 Files selected for processing (18)
  • app/src/components/settings/panels/MascotPanel.tsx
  • app/src/components/settings/panels/__tests__/MascotPanel.test.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/store/__tests__/mascotSlice.test.ts
  • app/src/store/mascotSlice.ts

Comment thread app/src/components/settings/panels/MascotPanel.tsx
Comment thread app/src/components/settings/panels/MascotPanel.tsx Outdated
Comment thread app/src/components/settings/panels/MascotPanel.tsx Outdated
Comment thread app/src/lib/i18n/bn.ts Outdated
Comment thread app/src/lib/i18n/de.ts Outdated
Comment thread app/src/lib/i18n/pt.ts Outdated
Comment thread app/src/lib/i18n/ru.ts Outdated
Comment thread app/src/lib/i18n/zh-CN.ts Outdated
Comment thread app/src/store/mascotSlice.ts
Comment thread app/src/store/mascotSlice.ts
The CEF native file panel only greyed-in PNG — a bare `image/jpeg`,
`image/gif` or `image/webp` MIME can end up unmapped, leaving those files
unselectable. Pair each MIME with its extension (the same convention the
JSON importers use) and add BMP, which the reducer and the MIME allowlist
already accept. `isAllowedMimeType` still gates the read, so this widens
the picker without widening what we accept.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

- Discard an avatar upload whose file read resolves after the user picked a
  different mascot, hit Save, or hit Reset. The slower read used to land last
  and resurrect the upload while wiping the selection the reducer had applied.
  Uses the same monotonic request-id pattern as the voice preview above it.
- Require structurally valid base64 in the data-URL pattern (whole quartets,
  at most one padded tail). The old `[A-Za-z0-9+/]+={0,2}` accepted payloads
  like `A=` that no image decoder can render, so the reducer persisted a
  silently broken avatar.
- Drop the FileReader error object from diagnostics — its message embeds
  `File.name`, which can be personal. Log the type and size instead.
- Add privacy-safe `[mascot-avatar] store:` diagnostics at the reducer
  boundary; a rejected value is otherwise invisible because the failure mode
  is a cleared avatar rather than an error. Source category plus length only.
- List BMP in the avatar error copy across all 15 locales (the reducer, the
  MIME allowlist, and the file picker already accepted it), and retire the
  leftover "custom GIF avatar" wording in settings.persona.appearanceDesc.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 6, 2026
…image

The shell installs no CefDialogHandler, so `accept` conversion falls to CEF's
built-in file-dialog runner, which does not expand an enumerated accept list
into selectable macOS file types. Both `image/png,image/jpeg,…` and those
MIMEs paired with `.png,.jpg,…` left every non-PNG image greyed out and
unselectable in the native panel. The wildcard goes through CEF's mime-table
expansion and offers every known image type; verified in a running dev build.

The picker widens but the contract does not: `isAllowedMimeType` still gates
the read, so SVG (inline-script risk) and any other type outside the allowlist
is rejected with a visible error rather than reaching the store.
@YellowSnnowmann

Copy link
Copy Markdown
Contributor Author

Follow-up after manual verification in a running dev build (5556b0a).

The upload button's file picker only offered PNG — every JPEG/GIF/WebP file was greyed out and unselectable in the native macOS panel. Two enumerated accept forms failed: MIME-only (image/png,image/jpeg,…) and MIME paired with extensions (…,.png,.jpg,…, the pairing the JSON importers use).

Root cause: the Tauri shell installs no CefDialogHandler, so accept conversion falls to CEF's built-in file-dialog runner, which doesn't expand an enumerated list into selectable macOS file types. Switched to the image/* wildcard, which goes through CEF's mime-table expansion. Confirmed working — WebP and JPEG are now selectable.

Worth noting the picker is now wider than the allowlist: SVG is offered by the dialog. The accepted set is unchanged, because isAllowedMimeType still gates the read — an SVG pick surfaces the "unsupported file type" error and never reaches the store.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@M3gA-Mind
M3gA-Mind merged commit d781949 into tinyhumansai:main Aug 6, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom avatars only accept .riv files — PNG and GIF not supported

2 participants