Release v0.51.295 — stage-3739/3742 (model-pick revert fix #3739 + session-status revert #3742) (#3743)
* fix: honor explicit model pick, suppress silent revert on cross-family selection (#3737)
When a user changes the model in the composer dropdown and sends,
_resolve_compatible_session_model_state previously had no way to
distinguish an explicit user pick from stale session state. The
profile-aware branch (v0.51.290, PR #3448) and the legacy block
both rewrote bare cross-family models to the profile default, and
the client unconditionally applied effective_model — silently
discarding the user's choice.
Backend: accept explicit_model_pick flag (default False) on
_resolve_compatible_session_model_state. Guard both the
profile-aware branch (routes.py:2024) and the legacy block
(routes.py:2124) to skip cross-provider normalization when set.
_handle_chat_start extracts the flag and passes it through.
Frontend: consult _readPendingSessionModel (sessionStorage, 10-min
window) to detect explicit picks and include the flag. Add a toast
as defense-in-depth when the server still returns effective_model.
Closes #3737
* fix: tighten explicit-pick detection and add regression tests (#3737)
Greptile P2-1: compare model_provider in pending pick detection,
not just model name, to avoid false-positive flag when the
session provider changes between pick and send.
Greptile P2-2: only show the defense-in-depth toast when an
explicit pick was actually overridden — stale-session
normalizations are expected behavior and should be silent.
Add two regression tests for the profile-branch guard:
- explicit_model_pick=True → cross-family model survives
- explicit_model_pick=False → existing normalization preserved
* revert(sidebar): remove manual session status labels (#3570)
The manual per-session status labels (Todo / In Progress / Done) added in
v0.51.284 (#3570) stored state only in browser localStorage keyed by session
id, with no server-side backing — so labels did not persist across browsers
or devices (a user who labeled sessions on one machine saw none after moving
to a laptop). They also rendered as three flat top-level entries in the
session context menu, crowding the root menu.
Per maintainer decision, remove the feature entirely for now. It can be
reintroduced later with proper server-side persistence and a less intrusive
menu treatment.
Removes:
- JS state/cycle helpers + SESSION_MANUAL_STATUS_KEY (static/sessions.js)
- context-menu status entries + sidebar status badge render
- .session-manual-status* CSS (static/style.css)
- session_status_* locale strings across all locales (static/i18n.js)
Full suite: 8084 passed, 0 failed. ESLint runtime gate: clean.
reverts #3570
* fix(#3737): keep explicit-pick marker until send consumes it (Codex catch)
Codex found the explicit_model_pick flag never engaged in the normal flow: boot.js
modelSelect.onchange cleared the pending-pick marker right after /api/session/update,
so by the time send() ran _readPendingSessionModel returned null, _explicitPick was
false, and the server's profile-provider branch still reverted the cross-family pick
(the exact #3737 bug). The flag only worked in the rare race where send beat the
session-update round-trip.
Fix (Codex prescription): do NOT clear the marker in onchange; clear it in send()
immediately after reading a matching pending pick, so it's consumed for that send only.
onchange still RECORDS the pick (_rememberPendingSessionModel) — only the premature
clear is removed.
* test(#3737): lock client clear-timing wiring (onchange records, send consumes)
Static source guards for the Codex clear-timing fix: onchange must record the
pending pick and NOT clear it post-session-update; send() must consume (clear) it
only after reading a matching _explicitPick, and send the flag only when truthy.
Complements the author's resolver-level tests in test_provider_mismatch.py.
* test(#3737): realign refresh-persistence test to the moved pending-pick clear
The Codex clear-timing fix moved the pending-pick clear out of modelSelect.onchange
into send() (consume-on-send). test_model_selection_records_pending_state_before_async_session_update
asserted the OLD onchange-clears behavior (assert _clearPendingSessionModel in body).
Updated to assert the NEW correct behavior (onchange must NOT clear it — it survives to
send). The test's core refresh-survives invariant (marker recorded before the async
session-update; reapplied on load) is unchanged and still passes; only the stale
clear-location assertion is flipped. Not a regression-blessing: the refresh-survives
feature is intact, the marker lifecycle is more correct.
---------
Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 20:39:14 +00:00
|
|
|
"""Regression guard for the #3737 explicit-pick CLIENT clear-timing fix (PR #3739 + Codex catch).
|
|
|
|
|
|
|
|
|
|
The server-side resolver coverage lives in test_provider_mismatch.py
|
|
|
|
|
(test_explicit_pick_survives_profile_family_mismatch / _false_allows_normalization).
|
|
|
|
|
|
|
|
|
|
This file locks the CLIENT-side wiring that makes the flag actually engage in the
|
|
|
|
|
normal flow. Codex found that boot.js modelSelect.onchange cleared the pending
|
|
|
|
|
explicit-pick marker right after /api/session/update, so by the time send() ran the
|
|
|
|
|
marker was gone, _explicitPick was false, and the server reverted the cross-family
|
|
|
|
|
pick anyway — the flag only worked in a rare race. The fix:
|
|
|
|
|
|
|
|
|
|
* onchange RECORDS the pick (_rememberPendingSessionModel) and must NOT clear it;
|
|
|
|
|
* send() consumes it (reads, then _clearPendingSessionModel) for that send only.
|
|
|
|
|
|
|
|
|
|
These are static source-structure assertions (the flow is DOM/network-driven and
|
|
|
|
|
exercised live); they keep the clear-timing from silently regressing.
|
|
|
|
|
"""
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
|
|
|
BOOT_JS = (REPO / "static" / "boot.js").read_text(encoding="utf-8")
|
|
|
|
|
MESSAGES_JS = (REPO / "static" / "messages.js").read_text(encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _model_onchange_region() -> str:
|
|
|
|
|
"""The modelSelect.onchange handler body where the pending pick is recorded and
|
|
|
|
|
the /api/session/update round-trip happens."""
|
|
|
|
|
idx = BOOT_JS.find("_rememberPendingSessionModel")
|
|
|
|
|
assert idx != -1, "_rememberPendingSessionModel call not found in boot.js"
|
|
|
|
|
# Window from the remember call through the session-update POST + its aftermath.
|
|
|
|
|
return BOOT_JS[idx: idx + 1200]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_onchange_records_pending_pick():
|
|
|
|
|
region = _model_onchange_region()
|
|
|
|
|
assert "_rememberPendingSessionModel(" in region, (
|
|
|
|
|
"modelSelect.onchange must record the explicit pick so send() can detect it"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_onchange_does_not_clear_pending_pick_after_session_update():
|
|
|
|
|
"""The premature clear (the #3737 bug) must be gone: onchange must NOT call
|
|
|
|
|
_clearPendingSessionModel after the /api/session/update POST."""
|
|
|
|
|
region = _model_onchange_region()
|
|
|
|
|
assert "_clearPendingSessionModel" not in region, (
|
|
|
|
|
"modelSelect.onchange must NOT clear the pending explicit-pick marker — it has "
|
|
|
|
|
"to survive until send() consumes it, else the normal pick→update→send flow "
|
|
|
|
|
"loses the explicit-pick signal and the server re-reverts the pick (#3737)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_send_consumes_pending_pick_after_reading_it():
|
|
|
|
|
"""send() must clear the marker once it has read a matching pending pick, so a
|
|
|
|
|
later send of an unchanged dropdown is not treated as a fresh explicit pick."""
|
|
|
|
|
idx = MESSAGES_JS.find("_explicitPick")
|
|
|
|
|
assert idx != -1, "_explicitPick not computed in send()"
|
|
|
|
|
region = MESSAGES_JS[idx: idx + 1100]
|
|
|
|
|
assert "_clearPendingSessionModel(activeSid)" in region, (
|
|
|
|
|
"send() must consume (clear) the pending explicit-pick marker after reading it"
|
|
|
|
|
)
|
2026-06-20 15:24:57 +00:00
|
|
|
# The clear must be gated on a genuine, matching pending pick (not on the
|
|
|
|
|
# broadened _explicitPick which may also be true from a cross-provider
|
|
|
|
|
# inference without a pending marker to consume).
|
|
|
|
|
assert re.search(r"_pendingPickMatch\s*&&[^\\n]*_clearPendingSessionModel", region), (
|
|
|
|
|
"the consume-clear must be gated on _pendingPickMatch (a genuine, matching pending pick)"
|
Release v0.51.295 — stage-3739/3742 (model-pick revert fix #3739 + session-status revert #3742) (#3743)
* fix: honor explicit model pick, suppress silent revert on cross-family selection (#3737)
When a user changes the model in the composer dropdown and sends,
_resolve_compatible_session_model_state previously had no way to
distinguish an explicit user pick from stale session state. The
profile-aware branch (v0.51.290, PR #3448) and the legacy block
both rewrote bare cross-family models to the profile default, and
the client unconditionally applied effective_model — silently
discarding the user's choice.
Backend: accept explicit_model_pick flag (default False) on
_resolve_compatible_session_model_state. Guard both the
profile-aware branch (routes.py:2024) and the legacy block
(routes.py:2124) to skip cross-provider normalization when set.
_handle_chat_start extracts the flag and passes it through.
Frontend: consult _readPendingSessionModel (sessionStorage, 10-min
window) to detect explicit picks and include the flag. Add a toast
as defense-in-depth when the server still returns effective_model.
Closes #3737
* fix: tighten explicit-pick detection and add regression tests (#3737)
Greptile P2-1: compare model_provider in pending pick detection,
not just model name, to avoid false-positive flag when the
session provider changes between pick and send.
Greptile P2-2: only show the defense-in-depth toast when an
explicit pick was actually overridden — stale-session
normalizations are expected behavior and should be silent.
Add two regression tests for the profile-branch guard:
- explicit_model_pick=True → cross-family model survives
- explicit_model_pick=False → existing normalization preserved
* revert(sidebar): remove manual session status labels (#3570)
The manual per-session status labels (Todo / In Progress / Done) added in
v0.51.284 (#3570) stored state only in browser localStorage keyed by session
id, with no server-side backing — so labels did not persist across browsers
or devices (a user who labeled sessions on one machine saw none after moving
to a laptop). They also rendered as three flat top-level entries in the
session context menu, crowding the root menu.
Per maintainer decision, remove the feature entirely for now. It can be
reintroduced later with proper server-side persistence and a less intrusive
menu treatment.
Removes:
- JS state/cycle helpers + SESSION_MANUAL_STATUS_KEY (static/sessions.js)
- context-menu status entries + sidebar status badge render
- .session-manual-status* CSS (static/style.css)
- session_status_* locale strings across all locales (static/i18n.js)
Full suite: 8084 passed, 0 failed. ESLint runtime gate: clean.
reverts #3570
* fix(#3737): keep explicit-pick marker until send consumes it (Codex catch)
Codex found the explicit_model_pick flag never engaged in the normal flow: boot.js
modelSelect.onchange cleared the pending-pick marker right after /api/session/update,
so by the time send() ran _readPendingSessionModel returned null, _explicitPick was
false, and the server's profile-provider branch still reverted the cross-family pick
(the exact #3737 bug). The flag only worked in the rare race where send beat the
session-update round-trip.
Fix (Codex prescription): do NOT clear the marker in onchange; clear it in send()
immediately after reading a matching pending pick, so it's consumed for that send only.
onchange still RECORDS the pick (_rememberPendingSessionModel) — only the premature
clear is removed.
* test(#3737): lock client clear-timing wiring (onchange records, send consumes)
Static source guards for the Codex clear-timing fix: onchange must record the
pending pick and NOT clear it post-session-update; send() must consume (clear) it
only after reading a matching _explicitPick, and send the flag only when truthy.
Complements the author's resolver-level tests in test_provider_mismatch.py.
* test(#3737): realign refresh-persistence test to the moved pending-pick clear
The Codex clear-timing fix moved the pending-pick clear out of modelSelect.onchange
into send() (consume-on-send). test_model_selection_records_pending_state_before_async_session_update
asserted the OLD onchange-clears behavior (assert _clearPendingSessionModel in body).
Updated to assert the NEW correct behavior (onchange must NOT clear it — it survives to
send). The test's core refresh-survives invariant (marker recorded before the async
session-update; reapplied on load) is unchanged and still passes; only the stale
clear-location assertion is flipped. Not a regression-blessing: the refresh-survives
feature is intact, the marker lifecycle is more correct.
---------
Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 20:39:14 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_send_sends_flag_only_when_explicit():
|
|
|
|
|
idx = MESSAGES_JS.find("_explicitPick")
|
|
|
|
|
region = MESSAGES_JS[idx: idx + 1300]
|
|
|
|
|
assert "explicit_model_pick:_explicitPick||undefined" in region.replace(" ", ""), (
|
|
|
|
|
"the chat/start payload must send explicit_model_pick only when truthy"
|
|
|
|
|
)
|