Inline fixes for 4 of 5 Opus SHOULD-FIX items before tag:
1. /api/auth/status now gates passkeys_enabled / passwordless_enabled on
_passkey_feature_flag_enabled() — when flag is off, status reports
no credentials even if passkeys.json has legacy entries. New
passkey_feature_flag field added to the response for the frontend.
2. Settings → System Passkeys block (passkeysSettingsBlock) now starts
display:none and loadPasskeys() reveals it only when the server
confirms passkey_feature_flag === true AND /api/auth/passkeys
doesn't return {disabled: true}. Stops the broken-affordance trap
where users would see Add passkey → click → 404.
3. /api/settings/save now refuses to set passwordless mode when the
passkey feature flag is off. Closes the auth-bypass path Opus flagged:
user goes passwordless while flag on → admin unsets flag → restart
serves the WebUI fully unauthenticated.
4. CHANGELOG entries added for PR #2685 (replayed-context dedup +
per-turn metering cap) and PR #2824 (Stop server affordance,
relocated to Settings) — both PRs had functional changes but no
release-notes entries. Also enriched the rate-limit detail on the
#2739 entry (30 events / 60s / 4KB body cap).
Deferred to follow-up issue (#5 in Opus review):
- Live tool metering cumulative cap across many tool calls — non-trivial
refactor of _bump_live_prompt_estimate, will be a separate PR
Adds the 7 shutdown-related i18n keys to all 10 non-en/tr locales
(it, ja, ru, es, de, zh, zh-Hant, pt, ko, fr) with proper translations.
Resolves test_*_locale.py::test_*_locale_covers_english_keys failures
that were caught by full sequential pytest. Locale parity is enforced
because untranslated keys would surface in non-en deployments as
English fallback text in the Stop Server affordance.
Italian + Portuguese translations use \' to escape apostrophes inside
the single-quoted JS string literals.
Per project deep-UX standards (default-hidden for niche destructive
actions). The title bar is shared real estate where always-visible
chrome competes with the title text and reload button — adding a
prominent destructive button there fails the 'kid clicks it' test even
with a confirmation modal. Moved to Settings → System where the user
who actively wants to stop the server can still find it, while everyone
else doesn't have to look at it.
Changes:
- Removed app-titlebar-shutdown button from <header> in index.html
- Removed dead .app-titlebar-shutdown CSS rule
- Added Settings → System → Stop server affordance (label + description + button)
- shutdownServer() and _showServerStopped() now use i18n keys
- Added 8 new locale keys to en + tr blocks (settings_label_shutdown,
settings_desc_shutdown, settings_btn_shutdown, settings_shutdown_confirm_*,
settings_shutdown_stopped_message). Other 9 locales fall back to English
via the existing locale fallthrough — follow-up issue tracked separately.
Preserves all of gavinssr's backend work (/api/shutdown route after CSRF
gate, BroadcastChannel for multi-tab signaling, app dialog with danger
styling) — only the placement is changed.
Add a power button (⏻) in the title bar that gracefully stops the
WebUI server process from the browser.
- api/routes.py: POST /api/shutdown endpoint with threaded os._exit(0)
- static/boot.js: shutdownServer() with confirm prompt, BroadcastChannel
cross-tab notification, and _showServerStopped() placeholder UI
- static/index.html: shutdown button HTML in title bar (after reload btn)
- static/style.css: .app-titlebar-shutdown styles, hover turns red
The cherry-pick of #2882 brought in an accidental two-space indent on a
zh-TW key. Restored the existing two-space indentation level so the
zh-CN clarification stays the only behavioural change.
PR #2882 was based on stale master (66de2367, pre-stage-batch7); naive
merge would delete 5,627 lines of subsequent work. Extracted the actual
zh-CN diff and applied it on top of fresh stage.
Co-authored-by: john <yuanchangjun@gmail.com>
My earlier conflict resolution between #2716 master and #2726 PR
dropped the 'const sessionModelState=...' assignment that the
.then() callback body uses on 6 different lines (1596, 1600, 1601,
1607, 1608, 1610). Without it boot.js would ReferenceError on every
boot. Caught by tests/test_new_chat_default_model_frontend.py::test_boot_model_hydration_prefers_active_session_over_persisted_model
which I'd missed in the initial touched-tests gate. Adds the
assignment back at the top of the .then() callback — semantically
matches the original #2716 master shape (S.session.model → wrap in
{model,model_provider} object, else null).
Cherry-picked via 3-way apply onto stage HEAD.
Resolved workspace.js conflict: kept master's #2716 sessionId-capture
stale-session guard (closure-scoped sessionId check after await), AND
added PR's renderSessionArtifacts() call to refresh the new Artifacts
tab when the file tree updates. Wrapped in typeof check for defense.
Co-authored-by: AJV20 <abdielvc@me.com>
Cherry-picked via 3-way apply onto stage HEAD (post-Release-A/B/C1).
Resolved boot.js conflict: took PR's parameterized
populateModelDropdown({preferProfileDefaultOnFreshBoot:true}) call
(the whole point of #2726) on top of master's #2716 boot path.
Co-authored-by: starship-s <starship-s@github.users.noreply.github.com>
- Add Turkish translations for 16 settings_aux_* / settings_label/desc_auxiliary_models
keys that #2680 added against the 10-locale set (pre-#2772 Turkish baseline).
- Bump test_auxiliary_models_settings.py::test_all_locales_have_auxiliary_keys
from count == 11 to count == 12 (one per locale, now including tr).
Cherry-picked via 3-way apply of net delta against stage HEAD. All 8 files
applied cleanly including the new static/pwa-startup.js.
Co-authored-by: AJV20 <abdielvc@me.com>
Cherry-picked via 3-way apply (rebase had failed on static/index.html
conflict when applied via rebase commit chain; 3-way of the net delta
against stage HEAD applied cleanly).
Co-authored-by: mccxj <mccxj@github.users.noreply.github.com>
Agent reviewer 'LGTM. Ship it.'
- Bug A fix: _session_field helper handles dict-vs-object snapshot in pin-limit check
- Bug B fix: removed stale client-side pinLimitReached short-circuit
- Bug C recovery: renderSessionList() on pin/unpin failure refreshes from server
Co-authored-by: franksong2702 <146128127+franksong2702@users.noreply.github.com>
nesquena APPROVED 2026-05-22. Cherry-picked onto post-v0.51.127
master via 3-way apply. Resolved api/routes.py conflict: master had
the inline correctness fix from the deep-review iteration; PR
refactors it into _metadata_only_message_summary() helper. Took the
helper AND added profile= threading (post-#2827 master adds
profile-aware state.db reads). Kept master's pre-existing
test_api_session_reload_drops_stale_cached_user_tail_after_saved_assistant
alongside the PR's new test_metadata_fast_path_matches_reconciliation_for_restamped_replays.
Co-authored-by: dobby-d-elf <dobby.the.agent@gmail.com>
MUST-FIX:
- tests/test_2735_open_in_vscode.py: bump expected open_in_vscode locale
counter from 10 to 11 (Turkish locale added in #2772). The bump fell
out of an in-rebase test edit but never got committed; tagging without
this would have shipped a failing test in the release commit.
SHOULD-FIX inline:
- api/updates.py: case-D drift in _select_apply_compare_ref. The original
#2855 fix used latest_tag in the past-tag predicate; the check side
uses current_tag (HEAD's nearest reachable tag) plus a 'behind == 0'
gate. They drift when HEAD is on an OLDER release tag with commits on
top AND a NEWER tag exists ('case D'): check correctly suggests
advancing to the newer tag, but apply fell through to origin/<branch>.
Mirror the check-side predicate exactly. Adds regression test
test_select_apply_compare_ref_case_d_older_tag_with_commits_and_newer_tag_exists.
- static/messages.js: post-await race guard in _restoreSettledSession.
stream_end without preceding 'done' enters the settlement path, awaits
/api/session, then sets _streamFinalized=true. If a late 'done' event
arrives during that await, it sees _streamFinalized still false and
double-runs the finalize. The guard returns early when done won the
race, avoiding double renderMessages() + double notification.
- server.py: CORS preflight Access-Control-Allow-Methods now includes PUT.
#2776 wired PUT into the router for /api/mcp/servers/{name} but didn't
update the OPTIONS response. Same-origin only in practice, but cosmetic
completeness for CORS-aware deployments.
Opus advisor verdict: all 5 risk areas reviewed, 1 MUST-FIX + 3 SHOULD-FIX
all addressed inline. Net: +69/-9, no new architecture, no behavior risk.
Replace \\u2026 with \u2026 (and fix \\u2192/\\u2713) in the tr block
so ellipsis renders as U+2026 instead of literal backslash-u text.
Add a regression test guarding against double-escaped unicode sequences.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a complete Turkish locale to the WebUI and login page so users can
select Türkçe in Settings, with speech recognition via tr-TR.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add `PATCH /api/mcp/servers/{name}` endpoint that accepts `{"enabled": bool}`,
updates `mcp_servers.<name>.enabled` in config.yaml, and calls `reload_config()`.
Mirrors the existing DELETE pattern.
Also wire the previously-defined-but-unrouted `_handle_mcp_server_delete` into
`handle_delete`, and `_handle_mcp_server_update` into a new `handle_put` +
`do_PUT` in server.py — fixing a pre-existing bug where those handlers existed
but were never reachable over HTTP.
UI: add a toggle button in each MCP server row in the system settings panel
(panels.js). Clicking it calls PATCH and reloads the list. Toggle button is
styled with `.mcp-toggle-enabled` / `.mcp-toggle-disabled` CSS classes. The
`toggle_supported` flag in the list response is now `True`.
i18n: add 5 new keys (`mcp_enable_server`, `mcp_disable_server`,
`mcp_enabled_toast`, `mcp_disabled_toast`, `mcp_toggle_failed`) to all 9
non-English locales (English values as placeholder translations).
Tests: add `TestMcpToggle` class with 7 tests covering disable, enable,
404-not-found, empty name, missing field, response payload, and URL-encoded name.
Update `test_empty_config` and visibility panel assertions to reflect
`toggle_supported: True` and the new toggle button in panels.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three small fixes from Copilot's review:
1. static/style.css:1354 - removed spaces inside `clamp(...)` args to
match the file's existing compact style (no spaces after commas in
neighboring declarations like `transition:border-color .2s,box-shadow .2s`).
2. CHANGELOG.md - wrapped the long single-line entry across multiple lines
with standard Markdown continuation indentation for cleaner diffs.
3. CHANGELOG.md - normalized `~1300 px` to `~1300px` for unit-formatting
consistency.
No behavior change. Same one-line CSS rule, just tightened formatting.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`.composer-box` had a hardcoded `max-width: 780px` since the early
v0.50.x layout pass. On wide displays (1440p+, 2880px ultrawides)
this leaves significant unused horizontal space AND squeezes the
composer-footer chips (workspace, model, reasoning, context %)
against each other inside the 780px box.
When the context-percentage ring appears (active token usage), the
workspace chip truncates to "Fou..." instead of showing the full
workspace name. Model + reasoning chips also lose room. The chip
strip horizontally-scrolls inside .composer-left, so the rightmost
chips effectively hide behind context %.
The constraint isn't "Reading flow looks better at 780px" — the
textarea is min-height:64px, max-height:200px and wraps naturally,
so users on wide displays get the SAME readable text wrap regardless
of box width. Only the footer chips suffer.
Fix: clamp(780px, 60vw, 1100px). Preserves the 780px floor (no
regression on viewports < 780px since clamp's first arg is the
minimum) while letting wider viewports use up to 1100px (60% of
viewport width, capped). 1100px gives ~40% more horizontal room for
the footer chips without filling the entire screen at extreme widths.
Per-viewport behavior:
<= 780 px → 780 px (hard floor) — zero change vs current
1280 px → 60vw = 768 → floored to 780 — zero change
1440 px → 60vw = 864 — +84 px room
1920 px → 60vw = 1152 → capped at 1100 — +320 px room
2880 px → 60vw = 1728 → capped at 1100 — +320 px room
One line in static/style.css. CHANGELOG entry. No JS. No new deps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Squashed from 2 author commits:
- d2237e23 feat: surface live activity timeline
- eee57ec0 fix: satisfy activity timeline CI guards
Frontend-only telemetry from existing stream events. Replaces empty
Thinking… placeholder with observable run status (Waiting on model /
Waiting on tool result / Working for …). New CSS, new test file.
- Rewrote _kanbanRenderMarkdown() from basic paragraph wrapper to a
line-by-line block processor supporting headings, code blocks, lists,
task lists, tables, blockquotes, horizontal rules, and strikethrough.
- Added CSS for all new elements (table borders, code blocks, checkboxes,
blockquote accent, heading sizing, etc.).
- Dropped white-space: pre-wrap from .kanban-task-preview-body and
.kanban-detail-row-main since markdown now handles layout.
- Applied _kanbanRenderMarkdown() to task description (was esc()) and
comment body (was esc()) in the task detail view.
Squashed from 2 author commits:
- a1017d02 initial fix: flex:0 0 auto on all 5 chip wraps
- bf54ba50 Copilot review fix-up: consolidate into single rule
Closes#2740. CSS-only, no JS changes. Default-width layout unchanged,
only affects narrow-viewport overflow regime via composer-left's existing
overflow-x:auto.
After the user responds to a clarify prompt, insert a synthetic user
message into the conversation showing their choice. This makes the
clarify interaction visible in the chat history, which was previously
only shown in the transient clarify dialog card.
The message is marked with _clarify_response: true so downstream
consumers can distinguish it from regular user messages if needed.
When tool approval or clarification cards appear during streaming,
they unconditionally call focus() on their input elements via setTimeout,
stealing focus from the composer (#msg) if the user is actively typing.
This silently drops keystrokes mid-type.
Add a guard: only move focus to the card if the composer textarea does
not already have focus. The document.activeElement check matches the
pattern already used upstream in other focus-sensitive components.
Fixes: #
Right-click any workspace file, folder, or root now shows
'Open in VS Code' alongside the existing Reveal in File Manager action.
- POST /api/file/open-vscode: resolves path via safe_resolve, finds VS
Code via shutil.which() with fallbacks for macOS (/usr/local/bin/code,
app bundle CLI), Linux (/usr/bin/code, /snap/bin/code), and Windows
(%LOCALAPPDATA% and %PROGRAMFILES% user/system installs). Returns a
descriptive error if not found rather than a bare OS error.
- Optional vscode block in config.yaml: command (default: code),
host_path_prefix + container_path_prefix for Docker path mapping.
- i18n: open_in_vscode and open_in_vscode_failed translated in all 10
locales (it, ja, ru, es, de, zh-CN, zh-TW, pt, ko).
- 26 tests in tests/test_2735_open_in_vscode.py covering source wiring,
command resolution, i18n completeness, and live endpoint error paths.
Two confirmed bugs in the thinking/reasoning display:
1. reasoningText was initialized once when the SSE stream opened and never
reset between turns. On the done event, the last assistant message
received the union of every turn's reasoning. Now reset at both turn
boundaries: tool (alongside existing liveReasoningText reset) and
interim_assistant (the other turn boundary where prior reasoning closes).
2. ui.js renderMessages preferred m.reasoning (which could be corrupted by
bug 1) over m.reasoning_content (the clean per-turn value from the
backend). The fallback now reads m.reasoning_content || m.reasoning.
Both fixes are needed: bug 2 alone cannot cover providers that stream
reasoning events without populating reasoning_content on the final API
message.
Updated test_streaming_race_fix.py to scope its reconnect-accumulator
guard to the _wireSSE preamble only, since turn-boundary resets inside
event listeners are intentional and correct.
9 new regression tests in test_issue2565_reasoning_accumulation.py.