Repository navigation
Make the conversation window and locale writable - #78
Merged
Merged
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
concierge | bcdbbd9 | Nov 01 2026, 09:44 PM |
Two of the gaps #71 listed. The worker read `ConversationConfig` and `Tenant.locale` and nothing could write either, so every tenant ran on the `prompt::DEFAULT_*` constants and whatever Accept-Language said at signup. `GET`/`PUT /api/settings` covers both, with the dashboard's Settings tab as the UI. Each knob is three-valued on the wire — absent leaves the stored value, `null` clears the override, a number sets it — because a form that can only set values can never return one to its default. The `idle_gap > handoff_cooldown` invariant is checked against the resolved pair, not the submitted one: lowering only the gap can collide with a default cooldown the request never mentioned. Validation is pure and tested directly (`apply_conversation`, `parse_locale`, `parse_currency`), since behind `require_tenant` an API test can't reach it. Also drops `PUT /api/billing/currency`: unused, and a second write path to the same field that passed unknown codes through `Currency::parse`, which answers INR for anything it doesn't know.
ananthb
force-pushed
the
operator-ui-and-settings
branch
from
November 1, 2026 21:41
32a3ed1 to
bcdbbd9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two of the gaps #71 listed: the conversation window and the locale pair were
read by the worker and writable by nobody, so every tenant ran on the
prompt::DEFAULT_*constants and whateverAccept-Languagesaid at signup.GET/PUT /api/settingsnow read and write both, and the dashboard'sSettings tab is the UI.
The three-valued wire format
Each knob is
Option<u32>, and "no override" is the state most accountsshould be in — so the body distinguishes three cases:
null— clear the override, back to the defaultWithout the
nullcase a form could only ever set values, never return one toits default. The form sends all three knobs every save, and an empty box
encodes as
null, so clearing a box is how you go back to the default. Theinputs show the default as a placeholder rather than pre-filling it, so saving
an untouched form doesn't quietly pin today's default forever.
The invariant
idle_gap_mins > handoff_cooldown_mins, checked against the resolvedpair rather than the submitted one. A cooldown that outlives the idle gap
would keep answering in the holding voice for a conversation the pipeline has
already ended.
Resolving first is what makes it correct: lowering only the gap to 45 is
refused because the default cooldown is 60, even though the request never
mentioned the cooldown. My own first test hit exactly that and had to be
fixed, which is the case a submitted-values-only check would have let through.
Why the validation is a pure function
Everything interesting — the three-way read, the bounds, the invariant — sits
behind
require_tenant, so an API test can't reach it without a session.apply_conversation,parse_localeandparse_currencyare pure and testeddirectly, the same shape as
risk.rs. 12 unit tests cover clearing, absentfields, string numbers (the form posts text), both ends of each range, and the
invariant against an unmentioned default.
api.spec.tspicks up/api/settingsin the signed-out 401 list and theX-Concierge-Requestlist, which is what it can prove without a session.Removed:
PUT /api/billing/currencyIt already existed, nothing in the frontend called it, and it wrote the same
Tenant.currencyfield — a second write path waiting to drift. It also passedunknown codes through
Currency::parse, which answers INR for anything itdoesn't recognise, so
{"currency":"EUR"}returned 204 and silently meantINR. The settings endpoint rejects instead, and
parse_currency's test coversthat specific case.
Docs
doc/configuration.htmlclaimed these had no endpoint and that "nothingenforces this at present" for the invariant. Both are now false, so all three
passages are rewritten, including the removed billing endpoint.
Checks
cargo check,clippy -D warnings,cargo fmt --check,elm-format --validateand 117 unit tests all pass. Full Playwright suite: 259 passed, exit 0(captured explicitly).
doc/screenshots/dashboard-settings.pngregenerated,and the prerendered pages with it — rebuilding
app.jschanges its hash, sothe committed snapshots go stale otherwise and SRI blocks the bundle.