release: v0.50.240
## Release v0.50.240
Batch release of 13 PRs that passed full triage + code review + test suite (3199 tests, 0 failures).
---
### Added
- **Compact tool activity mode** (`simplified_tool_calling`, default on) — groups tool calls and thinking traces into a single collapsed "Activity" disclosure card per assistant turn. Also adds a new **Calm Console** theme with earth/slate palette and serif prose. @Michaelyklam — #1282
- **PDF first-page preview** — `MEDIA:` `.pdf` files render a canvas thumbnail via PDF.js CDN (4 MB cap). **HTML sandbox iframe** — `.html`/`.htm` files render inline in a sandboxed `<iframe srcdoc>` (256 KB cap). 10 i18n keys × 7 locales. @bergeouss — #1280, closes #480 #482
- **Inline Excalidraw diagram preview** — `.excalidraw` files render as pure SVG (no external deps; rectangles, ellipses, diamonds, text, lines, arrows, freehand; 512 KB cap). @bergeouss — #1279, closes #479
- **Inline CSV table rendering** — fenced `csv` blocks and `MEDIA:` CSV files render as scrollable HTML tables with auto-separator detection. @bergeouss — #1277, closes #485
- **Inline SVG, audio, and video rendering** — SVG as `<img>`, audio as `<audio controls>`, video as `<video controls>`. @bergeouss — #1276, closes #481
- **Batch session select mode** — multi-select sessions for bulk Archive/Delete/Move. 11 i18n keys × 7 locales. @bergeouss — #1275, closes #568
- **Collapsible skill category headers** — click to collapse/expand without re-render; state persists across filter cycles. @bergeouss — #1281
- **`providers.only_configured` setting** — opt-in flag to restrict the model picker to explicitly configured providers. @KingBoyAndGirl — #1268
- **OpenCode Go model catalog** — adds Kimi K2.6, DeepSeek V4 Pro/Flash, MiMo V2.5/Pro, Qwen3.6/3.5 Plus. @nesquena-hermes — #1284, closes #1269
### Fixed
- **Profile `TERMINAL_CWD` TypeError** — `_build_agent_thread_env()` helper merges env before `_set_thread_env()` call. @hi-friday — #1266
- **Service worker subpath cache bypass** — regex now matches `/api/*` under any mount prefix. @Michaelyklam — #1278
- **SSE client disconnect leaks** — `TimeoutError`/`OSError` treated as clean disconnects; server backlog 64, threads daemonized; session list renders before saved-session restore. @KayZz69 — #1267
- **i18n locale corrections** — Korean MCP strings (23), Chinese MCP strings (23), zh-Hant missing keys (41), de missing keys (229). @bergeouss — #1274, closes #1273
---
### Test results
```
3199 passed, 2 skipped, 3 xpassed in 72.79s
```
### PRs on hold (not included)
#1265 (draft), #1271 (superseded by #1266), #1272 (skipped XSS tests), #1232 (partial test run), #1222 (review questions open), #1134 (live-server tests), #1132 (superseded by #1134), #1108 (negative UX review), #1084 (empty description)
2026-04-30 00:42:32 +00:00
|
|
|
"""Test: SVG, audio, video inline rendering (#481)"""
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_media_extension_regexes_exist():
|
|
|
|
|
"""Verify SVG/audio/video extension regexes are defined."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
assert '_SVG_EXTS' in src, "Missing _SVG_EXTS regex"
|
|
|
|
|
assert '_AUDIO_EXTS' in src, "Missing _AUDIO_EXTS regex"
|
|
|
|
|
assert '_VIDEO_EXTS' in src, "Missing _VIDEO_EXTS regex"
|
|
|
|
|
# Verify they test correct extensions
|
|
|
|
|
assert 'svg' in src, "SVG regex should match .svg"
|
|
|
|
|
assert 'mp3' in src, "AUDIO regex should match .mp3"
|
|
|
|
|
assert 'ogg' in src, "AUDIO regex should match .ogg"
|
|
|
|
|
assert 'mp4' in src, "VIDEO regex should match .mp4"
|
|
|
|
|
assert 'webm' in src, "VIDEO regex should match .webm"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_svg_rendered_before_image_catch_all():
|
|
|
|
|
"""Verify SVG handler for URLs runs before the catch-all image handler."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
# Find positions of SVG vs image catch-all in the URL section
|
|
|
|
|
svg_url_match = src.find("SVG URLs")
|
2026-04-30 02:54:07 +00:00
|
|
|
# Comment can say either variant of the catch-all description
|
release: v0.50.240
## Release v0.50.240
Batch release of 13 PRs that passed full triage + code review + test suite (3199 tests, 0 failures).
---
### Added
- **Compact tool activity mode** (`simplified_tool_calling`, default on) — groups tool calls and thinking traces into a single collapsed "Activity" disclosure card per assistant turn. Also adds a new **Calm Console** theme with earth/slate palette and serif prose. @Michaelyklam — #1282
- **PDF first-page preview** — `MEDIA:` `.pdf` files render a canvas thumbnail via PDF.js CDN (4 MB cap). **HTML sandbox iframe** — `.html`/`.htm` files render inline in a sandboxed `<iframe srcdoc>` (256 KB cap). 10 i18n keys × 7 locales. @bergeouss — #1280, closes #480 #482
- **Inline Excalidraw diagram preview** — `.excalidraw` files render as pure SVG (no external deps; rectangles, ellipses, diamonds, text, lines, arrows, freehand; 512 KB cap). @bergeouss — #1279, closes #479
- **Inline CSV table rendering** — fenced `csv` blocks and `MEDIA:` CSV files render as scrollable HTML tables with auto-separator detection. @bergeouss — #1277, closes #485
- **Inline SVG, audio, and video rendering** — SVG as `<img>`, audio as `<audio controls>`, video as `<video controls>`. @bergeouss — #1276, closes #481
- **Batch session select mode** — multi-select sessions for bulk Archive/Delete/Move. 11 i18n keys × 7 locales. @bergeouss — #1275, closes #568
- **Collapsible skill category headers** — click to collapse/expand without re-render; state persists across filter cycles. @bergeouss — #1281
- **`providers.only_configured` setting** — opt-in flag to restrict the model picker to explicitly configured providers. @KingBoyAndGirl — #1268
- **OpenCode Go model catalog** — adds Kimi K2.6, DeepSeek V4 Pro/Flash, MiMo V2.5/Pro, Qwen3.6/3.5 Plus. @nesquena-hermes — #1284, closes #1269
### Fixed
- **Profile `TERMINAL_CWD` TypeError** — `_build_agent_thread_env()` helper merges env before `_set_thread_env()` call. @hi-friday — #1266
- **Service worker subpath cache bypass** — regex now matches `/api/*` under any mount prefix. @Michaelyklam — #1278
- **SSE client disconnect leaks** — `TimeoutError`/`OSError` treated as clean disconnects; server backlog 64, threads daemonized; session list renders before saved-session restore. @KayZz69 — #1267
- **i18n locale corrections** — Korean MCP strings (23), Chinese MCP strings (23), zh-Hant missing keys (41), de missing keys (229). @bergeouss — #1274, closes #1273
---
### Test results
```
3199 passed, 2 skipped, 3 xpassed in 72.79s
```
### PRs on hold (not included)
#1265 (draft), #1271 (superseded by #1266), #1272 (skipped XSS tests), #1232 (partial test run), #1222 (review questions open), #1134 (live-server tests), #1132 (superseded by #1134), #1108 (negative UX review), #1084 (empty description)
2026-04-30 00:42:32 +00:00
|
|
|
image_catch_all = src.find("Render all https:// URLs as <img>")
|
|
|
|
|
assert svg_url_match > 0, "SVG URL handler not found"
|
|
|
|
|
assert image_catch_all > 0, "Image catch-all handler not found"
|
|
|
|
|
assert svg_url_match < image_catch_all, \
|
|
|
|
|
"SVG handler must come before image catch-all to avoid being shadowed"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_local_svg_inline_rendering():
|
|
|
|
|
"""Verify local SVG files render as inline image."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
assert "msg-media-svg" in src, "Missing msg-media-svg CSS class for SVG rendering"
|
|
|
|
|
# Should have at least 2 SVG handlers (URL + local)
|
|
|
|
|
count = src.count("msg-media-svg")
|
|
|
|
|
assert count >= 2, f"Expected >=2 msg-media-svg references, got {count}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_local_audio_inline_rendering():
|
|
|
|
|
"""Verify local audio files render as inline player."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
assert "msg-media-audio" in src, "Missing msg-media-audio CSS class"
|
|
|
|
|
assert "<audio controls" in src, "Should render <audio> element with controls"
|
|
|
|
|
count = src.count("msg-media-audio")
|
|
|
|
|
assert count >= 2, f"Expected >=2 msg-media-audio references, got {count}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_local_video_inline_rendering():
|
|
|
|
|
"""Verify local video files render as inline player."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
assert "msg-media-video" in src, "Missing msg-media-video CSS class"
|
|
|
|
|
assert "<video controls" in src, "Should render <video> element with controls"
|
|
|
|
|
count = src.count("msg-media-video")
|
|
|
|
|
assert count >= 2, f"Expected >=2 msg-media-video references, got {count}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_url_svg_audio_video_handlers():
|
|
|
|
|
"""Verify HTTPS URLs for SVG/audio/video get inline rendering."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
2026-04-30 02:54:07 +00:00
|
|
|
# SVG URLs should be handled via _SVG_EXTS test on urlPath
|
|
|
|
|
url_svg = "_SVG_EXTS.test(urlPath)" in src or ("_SVG_EXTS.test" in src and "urlPath" in src)
|
|
|
|
|
# Audio/video via mediaKindForName or explicit _AUDIO/_VIDEO tests
|
|
|
|
|
url_audio = src.count("_AUDIO_EXTS.test(src.split") + src.count("_AUDIO_EXTS.test(urlPath") + src.count("mediaKindForName")
|
|
|
|
|
url_video = src.count("_VIDEO_EXTS.test(src.split") + src.count("_VIDEO_EXTS.test(urlPath") + src.count("mediaKindForName")
|
release: v0.50.240
## Release v0.50.240
Batch release of 13 PRs that passed full triage + code review + test suite (3199 tests, 0 failures).
---
### Added
- **Compact tool activity mode** (`simplified_tool_calling`, default on) — groups tool calls and thinking traces into a single collapsed "Activity" disclosure card per assistant turn. Also adds a new **Calm Console** theme with earth/slate palette and serif prose. @Michaelyklam — #1282
- **PDF first-page preview** — `MEDIA:` `.pdf` files render a canvas thumbnail via PDF.js CDN (4 MB cap). **HTML sandbox iframe** — `.html`/`.htm` files render inline in a sandboxed `<iframe srcdoc>` (256 KB cap). 10 i18n keys × 7 locales. @bergeouss — #1280, closes #480 #482
- **Inline Excalidraw diagram preview** — `.excalidraw` files render as pure SVG (no external deps; rectangles, ellipses, diamonds, text, lines, arrows, freehand; 512 KB cap). @bergeouss — #1279, closes #479
- **Inline CSV table rendering** — fenced `csv` blocks and `MEDIA:` CSV files render as scrollable HTML tables with auto-separator detection. @bergeouss — #1277, closes #485
- **Inline SVG, audio, and video rendering** — SVG as `<img>`, audio as `<audio controls>`, video as `<video controls>`. @bergeouss — #1276, closes #481
- **Batch session select mode** — multi-select sessions for bulk Archive/Delete/Move. 11 i18n keys × 7 locales. @bergeouss — #1275, closes #568
- **Collapsible skill category headers** — click to collapse/expand without re-render; state persists across filter cycles. @bergeouss — #1281
- **`providers.only_configured` setting** — opt-in flag to restrict the model picker to explicitly configured providers. @KingBoyAndGirl — #1268
- **OpenCode Go model catalog** — adds Kimi K2.6, DeepSeek V4 Pro/Flash, MiMo V2.5/Pro, Qwen3.6/3.5 Plus. @nesquena-hermes — #1284, closes #1269
### Fixed
- **Profile `TERMINAL_CWD` TypeError** — `_build_agent_thread_env()` helper merges env before `_set_thread_env()` call. @hi-friday — #1266
- **Service worker subpath cache bypass** — regex now matches `/api/*` under any mount prefix. @Michaelyklam — #1278
- **SSE client disconnect leaks** — `TimeoutError`/`OSError` treated as clean disconnects; server backlog 64, threads daemonized; session list renders before saved-session restore. @KayZz69 — #1267
- **i18n locale corrections** — Korean MCP strings (23), Chinese MCP strings (23), zh-Hant missing keys (41), de missing keys (229). @bergeouss — #1274, closes #1273
---
### Test results
```
3199 passed, 2 skipped, 3 xpassed in 72.79s
```
### PRs on hold (not included)
#1265 (draft), #1271 (superseded by #1266), #1272 (skipped XSS tests), #1232 (partial test run), #1222 (review questions open), #1134 (live-server tests), #1132 (superseded by #1134), #1108 (negative UX review), #1084 (empty description)
2026-04-30 00:42:32 +00:00
|
|
|
assert url_svg, "URL SVG handler should test extension on src"
|
|
|
|
|
assert url_audio >= 1, "URL audio handler should test extension on src"
|
|
|
|
|
assert url_video >= 1, "URL video handler should test extension on src"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_attachment_svg_audio_video():
|
|
|
|
|
"""Verify file attachments for SVG/audio/video get inline previews."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
assert "attach-thumb--svg" in src, "Missing attach-thumb--svg for SVG thumbnails"
|
|
|
|
|
assert "attach-chip--audio" in src, "Missing attach-chip--audio"
|
|
|
|
|
assert "attach-chip--video" in src, "Missing attach-chip--video"
|
|
|
|
|
assert "attach-chip-media" in src, "Missing attach-chip-media label"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_attachment_blob_url_cleanup():
|
|
|
|
|
"""Verify audio/video attachment chips create blob URLs."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
# SVG and media attachments should use createObjectURL
|
|
|
|
|
assert "URL.createObjectURL(f)" in src, "Should create blob URLs for attachments"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_preload_metadata():
|
|
|
|
|
"""Verify audio/video elements use preload='metadata' for performance."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
assert 'preload="metadata"' in src, "Audio/video should use preload='metadata'"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_media_label_class():
|
|
|
|
|
"""Verify media label class exists for type identification."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
assert "msg-media-label" in src, "Missing msg-media-label class"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_i18n_keys():
|
|
|
|
|
"""Verify media rendering i18n keys exist in all locales."""
|
|
|
|
|
with open('static/i18n.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
required_keys = [
|
|
|
|
|
'media_audio_label',
|
|
|
|
|
'media_svg_label',
|
|
|
|
|
'media_video_label',
|
|
|
|
|
]
|
|
|
|
|
for key in required_keys:
|
|
|
|
|
count = src.count(f"{key}:")
|
|
|
|
|
assert count == 7, f"Key '{key}' found {count} times, expected 7"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_css_classes_exist():
|
|
|
|
|
"""Verify all media CSS classes are defined."""
|
|
|
|
|
with open('static/style.css') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
required_classes = [
|
|
|
|
|
'msg-media-svg',
|
|
|
|
|
'msg-media-label',
|
|
|
|
|
'msg-media-audio',
|
|
|
|
|
'msg-media-video',
|
|
|
|
|
'attach-thumb--svg',
|
|
|
|
|
'attach-chip--audio',
|
|
|
|
|
'attach-chip--video',
|
|
|
|
|
'attach-chip-media',
|
|
|
|
|
]
|
|
|
|
|
for cls in required_classes:
|
|
|
|
|
assert cls in src, f"Missing CSS class: .{cls}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_svg_not_matched_by_image_exts():
|
|
|
|
|
"""Verify .svg is NOT in _IMAGE_EXTS (SVG has its own handler)."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
# Extract the _IMAGE_EXTS regex
|
|
|
|
|
match = re.search(r"const _IMAGE_EXTS=/([^/]+)/i", src)
|
|
|
|
|
assert match, "Could not find _IMAGE_EXTS regex"
|
|
|
|
|
exts = match.group(1)
|
|
|
|
|
assert 'svg' not in exts.lower(), ".svg should NOT be in _IMAGE_EXTS"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_audio_video_not_matched_by_image_exts():
|
|
|
|
|
"""Verify audio/video extensions are NOT in _IMAGE_EXTS."""
|
|
|
|
|
with open('static/ui.js') as f:
|
|
|
|
|
src = f.read()
|
|
|
|
|
match = re.search(r"const _IMAGE_EXTS=/([^/]+)/i", src)
|
|
|
|
|
assert match
|
|
|
|
|
exts = match.group(1)
|
|
|
|
|
for ext in ['mp3', 'mp4', 'wav', 'ogg', 'webm', 'mov', 'm4a']:
|
|
|
|
|
assert ext not in exts.lower(), f".{ext} should NOT be in _IMAGE_EXTS"
|