Qt-removal R7.5d: composer thinking-mode toggle now has a real effect#152
Conversation
The composer's own "Thinking Mode" quick-access popover previously only
updated its own display label. backend/composer.py's set_reasoning_level
never called into api_provider.py, so the toggle had zero effect on the
next real chat call - which always used whatever the Ollama/Llama.cpp
Settings-page toggle (R7.4b/c) had last set instead.
Fixed by routing the composer's toggle through the same mechanism as
the Settings page, not a second parallel one. backend/settings.py gains
two shared functions - apply_ollama_reasoning_mode and
apply_llama_cpp_reasoning_mode - extracted from the persist-and-live-
reapply logic that used to live only inside setOllamaReasoningMode/
setLlamaCppReasoningMode's own closures; both intents are now thin
wrappers over them, and the five pre-existing regression tests for
that pair pass unmodified, confirming the extraction preserves
behavior exactly.
backend/composer.py's setReasoningLevel intent now normalizes the id,
branches on which local provider is actually live, calls the matching
shared function, and surfaces a Llama.cpp live-reapply failure through
the global notification banner (the composer has no settings-page-
local notice field to write into). It silently no-ops in cloud/API
mode, matching legacy, since the UI already disables the control
there. capabilities.reasoningSelection, previously hardcoded true, now
reflects the actually active provider - closing a second gap: the
composer's own prior-generation React island already disabled this
control while reasoningSelection was false or a request was busy; the
current SPA component had silently dropped both gates at some point
during the R2.3-R2.6 chrome consolidation. Composer.tsx now restores
disabled={!composer.capabilities.reasoningSelection ||
!composer.request.canSend} on the trigger, matching the island's own
established gating expression. The intent handler also gained legacy's
busy-guard, reusing the existing request.canSend computation rather
than adding a new field for it.
This increment ran through a Workflow: a design-synthesis agent
validated a hand-written recon brief against the actual code before
implementation began. It independently caught two real regressions the
brief missed - an existing backend test asserting the old hardcoded
true that would become order-dependent once the field went live, and
an existing frontend test relying on the fixture's default
canSend: false that would break the moment the trigger became gated -
and one real gap in the brief's own design, since the original proposal
never accounted for what should happen when a composer-triggered
Llama.cpp live-reapply actually fails. All three were fixed as part of
the same change.
Adversarial review of the finished diff found zero correctness defects:
the extraction preserves the original same-to_thread-hop race-avoidance
property, test_settings.py has a genuine zero-line diff, the busy-guard's
ordering matches legacy exactly, and request.canSend cannot get stuck
non-idle since every dispatch exit path in backend/agents.py runs
end_request in a finally block. It flagged one worthwhile test
strengthening - the Llama.cpp-failure test only asserted the
notification publish, not the trailing composer publish on the same
path - applied immediately.
Live-verified end-to-end against a real backend: selected Thinking Mode
in the composer, watched the trigger's label update, then read the
Settings topic back over a second WebSocket connection to the same
running backend and confirmed ollamaReasoningMode had genuinely become
"Thinking" - proof the click reached the real SettingsManager, not just
its own display state.
874 backend / 1204 frontend tests (7 new backend, 4 new/updated
frontend) pass, typecheck/lint/build clean, burn-down gate unchanged
at 152/84/68 (pure feature work), codegen untouched since the wire
type was already boolean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Second pass — the first fix was half a fixA requested adversarial re-audit of this diff found that R7.5d wired the write path and left the read path broken, so the two halves disagreed. Pushed as What was still wrong
Reproduced against the real This is arguably worse than the original bug: before, the toggle was inert and the label was meaningless decoration. After the first pass, the toggle genuinely wrote — which makes the label look authoritative while it starts out lying. Both sync directions were broken too, measured rather than assumed:
The fixRemove the second source of truth rather than sync two. Both reasoning intents now cross-publish the other surface's topic, guarded by a new Re-verifiedAll three reproductions now pass, plus a live browser check: the very reading that was wrong before now shows 6 new regression tests. 880 backend / 1204 frontend, lint/typecheck/build clean, burn-down gate unchanged at 152/84/68. Why every check missed itThis diff had a design-synthesis agent, an adversarial reviewer, and a live browser drive. All three missed it, because each was scoped to "is the write path correct?" — the question the increment posed — rather than "is the feature correct?". The browser drive literally rendered the wrong label and it was recorded as passing. A bidirectional binding needs both directions named in the review scope explicitly, or the unexamined direction survives every check. |
Fourth R7.5 parity sub-increment, after R7.5a (#147), R7.5b (#148/#149/#150), and R7.5c (#151).
The bug
The composer's own "Thinking Mode" quick-access popover only ever updated its own display label.
backend/composer.py'sset_reasoning_levelnever called intoapi_provider.py, so the toggle had zero effect on the next real chat call — which always used whatever the Ollama/Llama.cpp Settings-page toggle (R7.4b/c) had last set instead. Clicking the composer control looked like it worked and did nothing.The fix
Route the composer's toggle through the same mechanism as the Settings page, rather than a second parallel one:
backend/settings.pygains two shared functions —apply_ollama_reasoning_modeandapply_llama_cpp_reasoning_mode— extracted from the persist-and-live-reapply logic that used to live only insidesetOllamaReasoningMode/setLlamaCppReasoningMode's own closures. Both intents are now thin wrappers over them. The five pre-existing regression tests for that pair pass unmodified, confirming the extraction preserves behavior exactly (including the same-to_thread-hop race-avoidance property the original code relied on).backend/composer.py'ssetReasoningLevelintent normalizes the id, branches on which local provider is actually live (is_local_ollama_mode()/is_local_llama_cpp_mode(), mutually exclusive by construction), calls the matching shared function, and surfaces a Llama.cpp live-reapply failure through the global notification banner (composer has no settings-page-local notice field to write into). It silently no-ops in cloud/API mode, matching legacy, since the UI already disables the control there. It also gained legacy's busy-guard, reusing the existingrequest.canSendcomputation.capabilities.reasoningSelection, previously hardcodedtrue, now reflects the actually active provider. This closes a second, related gap: the composer's own prior-generation React island already disabled this control whenreasoningSelectionwas false or a request was busy — the current SPA component had silently dropped both gates somewhere during the R2.3–R2.6 chrome consolidation.Composer.tsxnow restoresdisabled={!composer.capabilities.reasoningSelection || !composer.request.canSend}, matching the island's own established expression.Process note
This increment ran through a Workflow: a design-synthesis agent validated a hand-written recon brief against the actual code before implementation began, rather than implementing straight off the brief. It independently caught two real regressions the brief missed:
trueforreasoningSelection— once that field went live off real module state, the assertion would become order-dependent on whatever any other test in the suite last leftapi_provider's globals as.canSend: false— once the trigger became gated on that value, the test's ownuser.click()would silently stop firing.It also found one real gap in the brief's own design: the original proposal never accounted for what should happen when a composer-triggered Llama.cpp live-reapply actually fails (it would have propagated as an uncaught, unexplained WS error). All three were fixed as part of the same change rather than discovered later as surprises.
Adversarial review
Zero correctness defects found. Confirmed: the extraction preserves the original race-avoidance property;
test_settings.pyhas a genuine zero-line diff; the busy-guard's bare-return-before-validation ordering matches legacy exactly;request.canSendcannot get stuck non-idle since every dispatch exit path inbackend/agents.pyrunsend_requestin afinally. One worthwhile strengthening was flagged and applied immediately: the Llama.cpp-failure test only asserted the"notification"publish, not the trailing"app-composer"publish on the same code path.Test plan
pytest backend/ tests/test_no_qt_anywhere.py) — 7 newnpm run check) — 4 new/updated, 0 lint errorsbool; only the computed value changed)app-settingstopic directly —ollamaReasoningModehad genuinely become"Thinking", proving the composer's click reached the realSettingsManager, not just its own display stateNote for the reviewer: the live verification above genuinely flipped the real
~/.graphlink/session.dat's persisted Ollama reasoning mode on the machine this was tested on — flagged there rather than silently reverted, since it's user-owned local state.