fix(app): stop the Team model selector deadlocking on its first pick - #22
Closed
Rwanbt wants to merge 1 commit into
Closed
fix(app): stop the Team model selector deadlocking on its first pick#22Rwanbt wants to merge 1 commit into
Rwanbt wants to merge 1 commit into
Conversation
The selector persisted every toggle, but the server schema requires at least two distinct models and answers 400 below that. Since `selected()` only advanced after a successful save, the first pick of an empty selection failed forever: reaching two models required passing through one. On a fresh Android install the Team selector was therefore permanently unusable, showing only "Failed to save global Team configuration" on every tap. Extract the toggle decision into a pure `planTeamToggle()` that stages sub-minimal selections locally and only persists once MIN_TEAM_MODELS is reached. `MIN_TEAM_MODELS` replaces the `2` that was hardcoded on both sides of the network boundary; the server schema stays the source of truth. The decision is pure because packages/app has no testing-library, so an inline guard in the JSX would have shipped without any test. Also in this commit: - Surface the server error detail when saving a Team or Debate configuration fails. A validation 400 and a network failure previously raised the same opaque message, which is what made this bug unreadable on device. - Relax `validateTeamSelection` to require two available models rather than all of them, and filter the persisted selection against connected providers on mount, mirroring DebateModelSelector. A single retired model no longer invalidates an otherwise usable selection. - Replace the raw NUL byte in the FAVORITES_GROUP sentinel with the `\0` escape. Same string value, but git no longer classifies the file as binary, so the component is diffable and reviewable again. Verified on a Mi 10 Pro (aarch64 release build): first pick no longer errors, the second persists, and the selection survives a restart. 695 app tests pass, including 8 new ones pinning that a sub-minimal selection is never sent.
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window. Feel free to open a new pull request that follows our guidelines. |
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.
Problem
On a fresh Android install, every tap in the Team models picker raised
Échec de la configuration Team / Failed to save global Team configuration, and no modelcould ever be checked. The Team selector was permanently unusable.
This is not the stale-sidecar / SPA-fallback bug fixed previously — verified: the
/team/configroute is present both in source and in the embedded mobile CLI bundle.Root cause
A cardinality contract that the client did not know about:
team-model-selector.tsxpersisted every toggle. Guards existed for dropping below 2and exceeding 8, but none for the
0 → 1ascent..min(2)(packages/opencode/src/team/selection.ts), enforcedby
validator("json", TeamSelection)onPUT /team/config→ one model is rejected 400.setSelected(next)ran afterawait save. The save always failed, soselected()stayed
[]and the next tap replayed the same 400.Reaching two models required passing through one, so it was structurally unreachable.
Desktop escaped it because the TUI dialog batches the whole selection behind a
ready()gateand only saves on confirm — users who configured there already had a valid stored selection
that
onMountreloaded.Fix
planTeamToggle()returningreject | stage | persist. Sub-minimal selections arestaged locally and never sent; persistence happens on the toggle that reaches the minimum.
Kept pure deliberately:
packages/apphas no testing-library, so an inline JSX guard wouldhave shipped untested.
MIN_TEAM_MODELSreplaces the2hardcoded on both sides of the network boundary. Theserver schema remains the source of truth.
and a network failure previously produced the same opaque toast, which is exactly what made
this bug unreadable on device.
validateTeamSelectionnow requires two available models rather than all of them, and thepersisted selection is filtered against connected providers on mount (mirrors
DebateModelSelector). One retired model no longer invalidates a usable selection.FAVORITES_GROUPsentinel used a raw NUL byte, which made git classify the file asbinary — no diffs, no review. Replaced with the
\0escape: identical string value, file istextual again.
Same-class sweep
All six call sites of
team.config/debate.config/.save()were inspected. Every otherone already gated correctly before persisting (
dialog-team-setup.tsxbatches behindready(),debate-model-selector.tsxreturns early below one participant,submit.tsgateson
isValid). No other occurrence of "incremental persistence against a minimum-cardinalityschema".
Verification
bun test --preload ./happydom.ts ./src→ 695 pass / 0 fail (687 + 8 new).bun run typecheck→ PASS.biome check→ clean.build): first pick no longer errors, second pick persists, selection survives a restart, and
removing below the minimum still shows the expected toast.
The 8 new tests pin the invariant directly: the first pick is staged and never sent, and
persistence only occurs at
MIN_TEAM_MODELS.Not included
The regenerated
packages/mobile/**/opencode-cli.jsbundles are left out. They are buildartifacts whose working-tree diff is dominated by CRLF churn and unrelated regeneration drift,
so they do not belong in this fix.