v0.50.207: batch of 10 PRs — TPS stat, SSE guard, session polish, cron UX, folder create, model errors, session speed, title gen#1031
Conversation
Three Traditional Chinese translation keys (cmd_status, memory_saved, profile_delete_title) were placed outside any locale block between the en and ru blocks in static/i18n.js. They became top-level properties of the LOCALES object, causing them to appear as invalid language options in the Settings > Preferences dropdown. The correct translations already exist in the zh-Hant locale block. Fixes #1008
- appendThinking(): guard with !S.session||!S.activeStreamId to drop events from a previous session's SSE stream during a session switch - appendLiveToolCard(): same guard for consistency - finalizeThinkingCard(): scroll thinking-card-body to top when scroll is pinned, so completed response is immediately visible - appendThinking(): auto-scroll thinking card body to bottom while streaming if user is watching (scroll pinned)
…ng status Fixes #995 — three sub-issues in the Cron Jobs UI: 1. Dual play icons ambiguous: Resume button now shows a distinct play+bar icon (play triangle + vertical line) instead of the identical triangle used by Run now. 2. Toast notification overlapping header buttons: Added position:relative; z-index:10 to .main-view-header so it stacks above the fixed toast (z-index:100 within its layer). 3. No running status after trigger: After triggering a job, the status badge immediately shows 'running…' with a CSS spinner animation, and polls the cron list every 3s (up to 30s) to refresh when the job completes. - Added cron_status_running i18n key in all 5 locales (en, es, de, ru, zh, zh-Hant) - Added .detail-badge.running CSS class with spinner animation - New functions: _setCronDetailStatus(), _startCronRunningPoll()
…, 30s fallback - _clearCronDetail() now clears _cronRunningPoll interval on navigation - Poll re-applies 'running' badge after loadCrons() re-render (prevents flicker) - When poll ends (30s max), detail re-renders with actual status as fallback
- After creating a folder via the file tree New folder button, offer to add it as a space via confirm dialog - Add Create folder if it doesnt exist checkbox in the New Space form - Backend: support create flag in /api/workspaces/add to mkdir before validation - i18n: 4 new keys (folder_add_as_space_title/msg/btn, workspace_auto_create_folder) in all 6 locales
Review feedback (critical): the previous code called mkdir() before validate_workspace_to_add(), which meant a rejected path (e.g. system dir) would leave an orphan directory on disk. New flow: 1. Resolve path and check against blocked system roots BEFORE any mutation 2. mkdir() only if path passes the blocklist check 3. Full validation (exists, is_dir) after mkdir Also imports _workspace_blocked_roots for the pre-mutation blocklist check.
- Add model_not_found error type to streaming.py exception classifier - Detect 404, 'not found', 'does not exist', 'invalid model' patterns - Strip HTML tags from provider error messages (nginx 404 pages, etc.) - Add model_not_found branch to apperror handler in messages.js - Add i18n key model_not_found_label in all 6 locales - 15 tests covering detection, sanitization, frontend, and i18n
Adds a TPS (Tokens Per Second) chip to the right of the header title bar that updates live while AI output is streaming. Metering (api/metering.py) - Tracks per-session output + reasoning tokens via GlobalMeter singleton - Per-session TPS = total_tokens / elapsed_time - Global TPS = average of active sessions' TPS values - HIGH/LOW are max/min of global_tps snapshots over a 60-minute rolling window (only recorded when > 0, so idle periods are excluded) - Thread-safe with a single lock Metering events emitted from streaming.py - Throttled at 100ms from token/reasoning/tool callbacks so the display updates rapidly during fast token streams - 1Hz ticker as fallback for slow streams (exits when no active sessions) - Final stats emitted on stream end Routes (api/routes.py) - Removed POST /api/metering/interval endpoint (dynamic interval via focus/blur was replaced with simple always-1s-when-active approach) UI (static/messages.js, index.html, style.css) - TPS chip in titlebar: shows 'N.N t/s . N.N high . N.N low' - Default: '0.0 t/s . 0.0 high' when idle - Display updates on every metering SSE event (throttled to 100ms)
…1026) PR #1025 (@franksong2702): Speed up large session restore paths - GET /api/session?messages=0 now parses only metadata before the messages array - Metadata-only loads no longer populate the full-session LRU cache - Frontend lazy fetch uses resolve_model=0 to avoid cold model-catalog lookup - Hard reload no longer waits for populateModelDropdown() before restoring session PR #1026 (@franksong2702): Harden auto title generation for reasoning models - Raises title-gen completion budget to 512 tokens (reasoning-safe) - Retries once with 1024 tokens on empty content / finish_reason:length - Applies retry to both auxiliary and active-agent fallback routes - Preserves underlying failure reason in title_status on local fallback Co-authored-by: Frank Song <[email protected]>
…mestamps (#1024) PR #1024 (@franksong2702): Polish session attention indicators - Streaming spinners and unread dots now reuse the right-side actions slot - Running/unread rows hide timestamps; idle/read rows keep right-aligned timestamps - Date group carets point down when expanded, right when collapsed - Pinned group no longer repeats pinned-star icon per row - Running indicators appear immediately after send (local busy state while /api/sessions catches up) - Sidebar sorting/grouping/timestamps now prefer last_message_at (derived from last real message) so metadata-only saves don't make old sessions appear under Today Co-authored-by: Frank Song <[email protected]>
nesquena
left a comment
There was a problem hiding this comment.
Review — release stage ✅ (clean, one CHANGELOG nit)
Stage composition
12 commits on top of master, mapping to 10 feature/fix PRs plus follow-up review-feedback commits, plus the release-notes commit:
| Commit | Author | Maps to |
|---|---|---|
| 7aaf255 | @bergeouss | #1010 i18n orphans |
| 5fcdd5c | @JKJameson | #1006 stale SSE guard |
| d754748 | @franksong2702 | #1009 empty agent sessions |
| 7a460cf | @bergeouss | #1011 cron UX |
| b3f057d | @bergeouss | #1011 review-feedback follow-up (poll cleanup, badge persistence) |
| 7bdeb69 | @bergeouss | #1018 create-folder UI |
| 20966ff | @bergeouss | #1018 review-feedback follow-up (pre-validate before mkdir) |
| 8142b92 | @bergeouss | #1022 model-not-found classifier |
| 486f040 | @JKJameson | #1005 TPS stat |
| 9ce017c | nesquena-hermes (reconstructed from @franksong2702 #1001/#1002) | #1025 + #1026 |
| de9325a | nesquena-hermes (reconstructed from @franksong2702 #1000) | #1024 |
| 82d39b1 | nesquena-hermes | release notes |
Authorship is preserved on every cherry-pick — git log --format confirms each contributor's noreply.github.com email is intact.
Per-PR approval state
All 10 bundled PRs have been individually reviewed and approved by nesquena reviewer in this batch session:
- #1005 — TPS stat ✅
- #1006 — stale SSE guard ✅
- #1009 — empty agent sessions ✅
- #1010 — i18n orphans ✅
- #1011 — cron UX ✅
- #1018 — create-folder UI ✅
- #1022 — model-not-found classifier ✅
- #1024 — session attention indicators ✅ (perf fix
eaca002was added during individual review) - #1025 — session restore speed ✅
- #1026 — title-gen reasoning ✅
The two follow-up commits (b3f057d, 20966ff) contain code that was already present in the individual PR branches I reviewed — they're review-feedback commits that landed before my individual reviews ran, not new surprise material.
Gate checks
- Test suite:
2121 passed, 47 skipped, 1 deselected(test_workspace_add_rejects_system_paths fails on master too — pre-existing, unrelated). Total = 2169 matching the release-notes claim. Net +36 vs v0.50.206 (2085 → 2121), which aligns with the new tests added by:- #1010: 2 (
test_locale_structure.py) - #1018: 0 (existing
test_workspace_path_validation.pyalready covers blocked-roots) - #1009: 2 (gateway sync regression tests)
- #1011: 0 (UX-only)
- #1006: 0 (UX-only)
- #1022: 15 (
test_issue1014_model_not_found.py) - #1005: 0 (no unit tests for GlobalMeter — noted in individual review as future work)
- #1024: 6 (pinned indicator + relative time + session index)
- #1025: 8 (metadata fast path + session index)
- #1026: 5 (title aux retry + status preservation)
- Total ≈ 38 new — consistent with the +36 advertised (a few tests may be reorganized)
- #1010: 2 (
- Python syntax:
ast.parseclean on all 6 modified.pyfiles - JS syntax:
node --checkclean on all 6 modified.jsfiles
Minor CHANGELOG nit (non-blocking)
The previous [Unreleased] section contained a "Reasoning chip now appears after the model chip" bullet which has been removed by the release-notes commit (82d39b1) without being re-attributed to a release section. The actual code change shipped much earlier (commit 87d4136 / PR #937 / v0.50.185 timeframe), so the [Unreleased] entry was already stale. Net effect: that change is now undocumented in any release section's CHANGELOG.
Severity: cosmetic. The change has been live for many releases, users won't be surprised by anything. A follow-up patch could backfill the v0.50.185 entry if release-history accuracy matters.
Coordination state
The bundled [#1024]/[#1025]/[#1026] reconstructed PRs were the explicit independent-review trigger for this stage PR (per the PR body's merge soon label state; I've reviewed and approved each on its own merits.
The "PRs held" table in the body correctly identifies #684, #928, #943, #952, #965, #1007, #1012, #1015, #1016, #1017, #1027 as out-of-scope for this batch with documented reasons.
Security audit (stage-level)
- No new endpoints reach this batch beyond what each individual PR introduced (all reviewed individually).
- The largest new attack surface is
/api/workspaces/addwithcreate:true(#1018), which now pre-validates against_workspace_blocked_roots()before any filesystem mutation. Audited in the #1018 individual review. ✓ - The metering subsystem (#1005) operates on server-internal counters with no user input. ✓
- HTML sanitization (#1022) strips tags from provider error messages before display. ✓
Recommendation
Clean release stage. Each bundled PR has been individually approved end-to-end. The cherry-pick history preserves authorship. Test count claim verified. The minor CHANGELOG nit doesn't affect users.
✅ Approved. Ready for release tag and merge to master.
|
Thanks for assembling this batch PR, @nesquena — clean gate results (2169 passed, +36 new tests, zero failures). A few notes for review: Reconstructed PRs require maintainer approval before merge — as called out in the PR body: #1024, #1025, and #1026 were reconstructed from @franksong2702's closed PRs #1000–#1002 per project convention. All three have received code review (LGTM comments posted), but they need a sign-off from Included PR status summary:
One open item: PR #1011 was flagged by @aronprins for missing screenshots. @bergeouss has been asked to add them. This may need to resolve before merge depending on maintainer preference. Overall the batch looks solid. Awaiting maintainer review of the three reconstructed PRs. 🙏 |
…n UX, folder create, model errors, session speed, title gen (nesquena#1031) * fix: remove orphaned i18n keys from top-level LOCALES object Three Traditional Chinese translation keys (cmd_status, memory_saved, profile_delete_title) were placed outside any locale block between the en and ru blocks in static/i18n.js. They became top-level properties of the LOCALES object, causing them to appear as invalid language options in the Settings > Preferences dropdown. The correct translations already exist in the zh-Hant locale block. Fixes nesquena#1008 * fix: block stale SSE events from polluting new session's DOM - appendThinking(): guard with !S.session||!S.activeStreamId to drop events from a previous session's SSE stream during a session switch - appendLiveToolCard(): same guard for consistency - finalizeThinkingCard(): scroll thinking-card-body to top when scroll is pinned, so completed response is immediately visible - appendThinking(): auto-scroll thinking card body to bottom while streaming if user is watching (scroll pinned) * Fix empty agent sessions in sidebar * fix: resolve cron UI UX issues — icon ambiguity, toast overlap, running status Fixes nesquena#995 — three sub-issues in the Cron Jobs UI: 1. Dual play icons ambiguous: Resume button now shows a distinct play+bar icon (play triangle + vertical line) instead of the identical triangle used by Run now. 2. Toast notification overlapping header buttons: Added position:relative; z-index:10 to .main-view-header so it stacks above the fixed toast (z-index:100 within its layer). 3. No running status after trigger: After triggering a job, the status badge immediately shows 'running…' with a CSS spinner animation, and polls the cron list every 3s (up to 30s) to refresh when the job completes. - Added cron_status_running i18n key in all 5 locales (en, es, de, ru, zh, zh-Hant) - Added .detail-badge.running CSS class with spinner animation - New functions: _setCronDetailStatus(), _startCronRunningPoll() * fix(nesquena#1011): address review feedback — poll cleanup, badge persistence, 30s fallback - _clearCronDetail() now clears _cronRunningPoll interval on navigation - Poll re-applies 'running' badge after loadCrons() re-render (prevents flicker) - When poll ends (30s max), detail re-renders with actual status as fallback * feat: create folder and add space directly from UI (nesquena#782) - After creating a folder via the file tree New folder button, offer to add it as a space via confirm dialog - Add Create folder if it doesnt exist checkbox in the New Space form - Backend: support create flag in /api/workspaces/add to mkdir before validation - i18n: 4 new keys (folder_add_as_space_title/msg/btn, workspace_auto_create_folder) in all 6 locales * fix: validate workspace path before mkdir to prevent orphan directories Review feedback (critical): the previous code called mkdir() before validate_workspace_to_add(), which meant a rejected path (e.g. system dir) would leave an orphan directory on disk. New flow: 1. Resolve path and check against blocked system roots BEFORE any mutation 2. mkdir() only if path passes the blocklist check 3. Full validation (exists, is_dir) after mkdir Also imports _workspace_blocked_roots for the pre-mutation blocklist check. * fix(nesquena#1014): classify model-not-found errors with helpful message - Add model_not_found error type to streaming.py exception classifier - Detect 404, 'not found', 'does not exist', 'invalid model' patterns - Strip HTML tags from provider error messages (nginx 404 pages, etc.) - Add model_not_found branch to apperror handler in messages.js - Add i18n key model_not_found_label in all 6 locales - 15 tests covering detection, sanitization, frontend, and i18n * feat(ui): add live TPS stat to header Adds a TPS (Tokens Per Second) chip to the right of the header title bar that updates live while AI output is streaming. Metering (api/metering.py) - Tracks per-session output + reasoning tokens via GlobalMeter singleton - Per-session TPS = total_tokens / elapsed_time - Global TPS = average of active sessions' TPS values - HIGH/LOW are max/min of global_tps snapshots over a 60-minute rolling window (only recorded when > 0, so idle periods are excluded) - Thread-safe with a single lock Metering events emitted from streaming.py - Throttled at 100ms from token/reasoning/tool callbacks so the display updates rapidly during fast token streams - 1Hz ticker as fallback for slow streams (exits when no active sessions) - Final stats emitted on stream end Routes (api/routes.py) - Removed POST /api/metering/interval endpoint (dynamic interval via focus/blur was replaced with simple always-1s-when-active approach) UI (static/messages.js, index.html, style.css) - TPS chip in titlebar: shows 'N.N t/s . N.N high . N.N low' - Default: '0.0 t/s . 0.0 high' when idle - Display updates on every metering SSE event (throttled to 100ms) * feat: session restore speed + title gen reasoning hardening (nesquena#1025, nesquena#1026) PR nesquena#1025 (@franksong2702): Speed up large session restore paths - GET /api/session?messages=0 now parses only metadata before the messages array - Metadata-only loads no longer populate the full-session LRU cache - Frontend lazy fetch uses resolve_model=0 to avoid cold model-catalog lookup - Hard reload no longer waits for populateModelDropdown() before restoring session PR nesquena#1026 (@franksong2702): Harden auto title generation for reasoning models - Raises title-gen completion budget to 512 tokens (reasoning-safe) - Retries once with 1024 tokens on empty content / finish_reason:length - Applies retry to both auxiliary and active-agent fallback routes - Preserves underlying failure reason in title_status on local fallback Co-authored-by: Frank Song <[email protected]> * feat: session attention indicators in right slot + last_message_at timestamps (nesquena#1024) PR nesquena#1024 (@franksong2702): Polish session attention indicators - Streaming spinners and unread dots now reuse the right-side actions slot - Running/unread rows hide timestamps; idle/read rows keep right-aligned timestamps - Date group carets point down when expanded, right when collapsed - Pinned group no longer repeats pinned-star icon per row - Running indicators appear immediately after send (local busy state while /api/sessions catches up) - Sidebar sorting/grouping/timestamps now prefer last_message_at (derived from last real message) so metadata-only saves don't make old sessions appear under Today Co-authored-by: Frank Song <[email protected]> * docs: v0.50.207 release notes — 10 PRs, 2169 tests (+36) --------- Co-authored-by: bergeouss <[email protected]> Co-authored-by: Josh <[email protected]> Co-authored-by: Frank Song <[email protected]> Co-authored-by: nesquena-hermes <[email protected]>
v0.50.207 — Batch of 10 PRs reviewed, fixed, and tested
This stage branch accumulates 10 contributor and self-built PRs reviewed and merged end-to-end.
Included PRs
Gate results
py_compilenode --checkpasses on modified JS filesPRs held (not in this batch)
merge soonlabeled)