fix(contacts): adversarial-review fixes — delete UB, JPEG decode, footer clip

From an adversarial review of the avatar edit-dialog + image picker:

- HIGH: the per-row Delete icon called doDelete() (which erases from
  book.entries()) INSIDE the loop iterating that same vector — out-of-bounds
  reads / wrong rows on the confirming click. Defer it until after the loop.
- HIGH: the decode stack was compiled PNG-only (STBI_ONLY_PNG) while the image
  picker accepts .jpg/.jpeg/.bmp/.gif, so picking a JPEG (the common photo
  case) silently produced a non-loading avatar + an orphaned copy on disk.
  Enable JPEG/BMP/GIF decoders, and guard the pick: verify the source decodes
  before copying/committing (new contact_avatar_bad_image string, 8 langs).
- MED: the fixed, non-scrolling edit card floored bodyH at 260*dp, which could
  push Save/Cancel below the card on short windows — floor lowered so the
  footer always stays inside.
- LOW: the live-preview panel rounding is now dp-scaled (10*dp) to match the
  real list card it mirrors.
- LOW: re-picking the same source path after its contents changed showed a
  stale cached texture — evict the avatar texture cache entry on re-pick.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 09:49:34 -05:00
parent e1df5ea798
commit 9649654c3c
11 changed files with 43 additions and 5 deletions

View File

@@ -288,6 +288,7 @@
"console_zoom_in": "Vergrößern",
"console_zoom_out": "Verkleinern",
"contact_avatar": "AVATAR",
"contact_avatar_bad_image": "Dieses Bild konnte nicht geladen werden.",
"contact_avatar_badge": "Abzeichen",
"contact_avatar_badge_hint": "Das Abzeichen wird automatisch anhand des Adresstyps gewählt.",
"contact_avatar_choose": "Bild auswählen…",

View File

@@ -288,6 +288,7 @@
"console_zoom_in": "Acercar",
"console_zoom_out": "Alejar",
"contact_avatar": "AVATAR",
"contact_avatar_bad_image": "No se pudo cargar esa imagen.",
"contact_avatar_badge": "Insignia",
"contact_avatar_badge_hint": "La insignia se elige automáticamente según el tipo de dirección.",
"contact_avatar_choose": "Elegir imagen…",

View File

@@ -288,6 +288,7 @@
"console_zoom_in": "Agrandir",
"console_zoom_out": "Réduire",
"contact_avatar": "AVATAR",
"contact_avatar_bad_image": "Cette image n'a pas pu être chargée.",
"contact_avatar_badge": "Badge",
"contact_avatar_badge_hint": "Le badge est choisi automatiquement selon le type d'adresse.",
"contact_avatar_choose": "Choisir une image…",

View File

@@ -288,6 +288,7 @@
"console_zoom_in": "拡大",
"console_zoom_out": "縮小",
"contact_avatar": "アバター",
"contact_avatar_bad_image": "その画像を読み込めませんでした。",
"contact_avatar_badge": "バッジ",
"contact_avatar_badge_hint": "バッジはアドレスの種類に応じて自動的に選ばれます。",
"contact_avatar_choose": "画像を選択…",

View File

@@ -288,6 +288,7 @@
"console_zoom_in": "확대",
"console_zoom_out": "축소",
"contact_avatar": "아바타",
"contact_avatar_bad_image": "그 이미지를 불러올 수 없습니다.",
"contact_avatar_badge": "배지",
"contact_avatar_badge_hint": "배지는 주소 유형에 따라 자동으로 선택됩니다.",
"contact_avatar_choose": "이미지 선택…",

View File

@@ -288,6 +288,7 @@
"console_zoom_in": "Aumentar zoom",
"console_zoom_out": "Diminuir zoom",
"contact_avatar": "AVATAR",
"contact_avatar_bad_image": "Não foi possível carregar essa imagem.",
"contact_avatar_badge": "Selo",
"contact_avatar_badge_hint": "O selo é escolhido automaticamente conforme o tipo de endereço.",
"contact_avatar_choose": "Escolher imagem…",

View File

@@ -288,6 +288,7 @@
"console_zoom_in": "Увеличить",
"console_zoom_out": "Уменьшить",
"contact_avatar": "АВАТАР",
"contact_avatar_bad_image": "Не удалось загрузить это изображение.",
"contact_avatar_badge": "Значок",
"contact_avatar_badge_hint": "Значок выбирается автоматически по типу адреса.",
"contact_avatar_choose": "Выбрать изображение…",

View File

@@ -288,6 +288,7 @@
"console_zoom_in": "放大",
"console_zoom_out": "缩小",
"contact_avatar": "头像",
"contact_avatar_bad_image": "无法加载该图片。",
"contact_avatar_badge": "标记",
"contact_avatar_badge_hint": "标记会根据地址类型自动选择。",
"contact_avatar_choose": "选择图片…",