fix(wizard): use ModelPicker for model selection#38
Conversation
✅ No Blockers —
|
| kimi-code | deepseek | aggregate | |
|---|---|---|---|
| Readiness | 93 | 92 | 92 |
| Confidence | 96 | 95 | 95 |
| Correctness | 93 | 95 | 93 |
| Security | 96 | 100 | 96 |
| Testing | 90 | 95 | 90 |
| Architecture | 93 | 90 | 90 |
I read every changed file, ran the full test suite (221 tests passed, 1 build-order failure unrelated to this PR), and verified TypeScript compilation. The PR cleanly replaces the flat HTML
<select>with the richModelPickercomponent, removes the fictitiousmodelTierdefaults from templates, and adds a correct deploy gate that blocks submission while models are loading but allows bare-mode deploys. The logic in the auto-select effect and the reset paths is sound and well covered by new tests. | Read all 7 changed files in full, ran tests (50/50 wizard tests passed, typecheck clean), ver
🟡 LOW CHANGELOG omits TemplatePreset type-level breaking change — CHANGELOG.md
Line 3-5: the changelog documents the removal of
ModelOptionand themodelOptionsprop, but does not mention thatTemplatePresetno longer containsmodelTier(and thatgetPresetForTemplatereturn values have changed accordingly). Consumers who type againstTemplatePresetdirectly will see a compile-time break. Fix: add a one-line note thatTemplatePresetnow omitsmodelTierso templates delegate model selection to the wizard's auto-select.
🟡 LOW Test no longer exercises handleDeploy early-return path — src/pages/provisioning-wizard.test.tsx
Lines ~192-201: the test 'does not call onSubmit when onSubmit prop is undefined' renders
<ProvisioningWizard variant="flat" />without amodelsprop. Because the new deploy gate disables the button whenmodelTier=""andbare=false, this test now clicks a disabled button and never reacheshandleDeploy. It still passes, but it no longer verifies the early-return safety path it claims to cover. Fix: passmodels={TEST_MODELS}so the button is enabled and the early-return path is actually exercised.
🟡 LOW Dead loading state in ModelPicker integration — src/pages/provisioning-wizard.tsx
At line 1095–1103,
loading={!models}is always accompanied bydisabled={!models || models.length === 0}, so the trigger is always disabled when loading is true. Theloadingprop renders a spinner inside the dropdown (model-picker.tsx:325) and shows 'Loading models...' text (line 376), but the dropdown can never be opened when the trigger is disabled. The props are correct — no user-visible defect — butloadingis effectively dead code that communicates no additional state to the user. Consider removingloadinga
tangletools · 2026-05-07T03:26:58Z · trace
This comment was marked as outdated.
This comment was marked as outdated.
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 3 non-blocking findings
I read all four changed files, verified callers/callees, ran the full test suite (51/51 provisioning-wizard tests pass, TypeScript compiles clean), and confirmed the ModelPicker integration. The PR is a well-structured backwards-compatible upgrade from a native to the rich ModelPicker component. The only real defect is a stale conditional that can render a misleading disabled-options banner when both models and modelOptions are supplied. | Replaces a native with the full ModelPicker in the provisioning wizard, adding support for rich Tangle Router model data (search, prov
🟠 MEDIUM Misleading all-disabled banner when both props are passed — src/pages/provisioning-wizard.tsx
Lines 1154–1161 check
modelOptions.every((o) => o.disabled)to render a disabled banner. BecausepickerModelsprioritizesmodelswhen both props are passed, a caller could supply a populatedmodelsarray alongside an all-disabledmodelOptionsfallback and the banner would still appear—even though the picker is fully functional. Fix: gate the banner onpickerModels.length === 0or add!modelsto the existing condition so it only shows when the actually-used list is empty/disabled.
🟡 LOW all-disabled banner shows when models prop overrides modelOptions — src/pages/provisioning-wizard.tsx
Lines 1154-1161: the all-disabled banner checks
modelOptions.every(o => o.disabled)against the rawmodelOptionsprop. When bothmodelsandmodelOptionsare passed (with modelOptions entries all disabled),pickerModelsusesmodelsso the picker shows live models, but the banner still renders beneath it with the misleading 'All model options are currently disabled' message. Fix: gate the banner onpickerModels.length === 0 && modelOptions && modelOptions.length > 0 && modelOptions.every(o => o.disabled), or suppress it entirely whenmodelsis provided.
🟡 LOW no loading indicator passed to ModelPicker during async model fetch — src/pages/provisioning-wizard.tsx
Lines 1146-1153: the ModelPicker is instantiated without a
loadingprop. When a caller passesmodels={[]}while the router API is still loading, the picker renders disabled with 'No models available.' copy instead of a spinner. This is not a regression (the old had no loading state either), but the ModelPicker component supports a loading prop for exactly this case. Consider exposing a modelsLoading prop or deriving loading state internally. tangletools · 2026-05-07T01:59:59Z · trace
Replaces the flat HTML <select> in the provisioning wizard's AI agent step with the existing ModelPicker — search, provider grouping, presets, pricing, and context-length display in one component. The wizard now takes `models: ModelInfo[]` (the wire-format payload from Tangle Router's /v1/models) and passes it straight to the picker. Drops the ModelOption/modelOptions API, DEFAULT_MODEL_OPTIONS fallback, and all-disabled banner — sandbox-ui has a single internal consumer, so the legacy compatibility surface is dead weight. While models is undefined the picker renders a disabled trigger with its built-in loading state. Adds a `triggerClassName` prop to ModelPicker so the wizard can size its trigger to match neighboring form fields (rounded-xl, h-12, font-bold) without overriding the picker's chat-input variant defaults.
8cf2c0a to
0a6e192
Compare
This comment was marked as outdated.
This comment was marked as outdated.
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 2 non-blocking findings
I read all four changed files, their full context, and ran the test suite (48 provisioning-wizard tests pass, 219 total tests pass, build succeeds, tsc clean). The PR cleanly migrates the wizard from a bespoke
<select>+ModelOption[]to the shared<ModelPicker>+ModelInfo[]. The code is well-commented and the new tests cover grouping, search, and auto-selection. Two issues prevent a higher score: a breaking public API change on the./pagessubpath export, and a behavioral regression where omittingmodelssubmits an emptymodelTierinstead of the previous hardcoded default.
🟠 MEDIUM Breaking public API: ModelOption and modelOptions removed — src/pages/index.ts
The
ModelOptioninterface andmodelOptionsprop are removed fromProvisioningWizardProps, andModelOptionis dropped from thesrc/pages/index.tsbarrel export. Becausepackage.jsonexports"./pages"as a public subpath (@tangle-network/sandbox-ui/pages), this is a breaking change for any external consumer importingModelOptionor passingmodelOptions. The PR replaces it withModelInfoandmodels, but there is no deprecation shim. If this breakage is intentional, it should be called out in release notes.
🟠 MEDIUM Empty modelTier submitted when models prop is omitted — src/pages/provisioning-wizard.tsx
Line 511:
const [modelTier, setModelTier] = React.useState(dc?.modelTier ?? "");. Whenmodelsis omitted anddefaultConfighas nomodelTier, state initializes to"". The auto-correct effect (lines 521-527) returns early becausemodelsis undefined, somodelTierstays empty.handleDeploy(line 594) submits""toonSubmit. The old code guaranteed a non-empty fallback (`DEFAUL
tangletools · 2026-05-07T02:19:14Z · trace
This comment was marked as outdated.
This comment was marked as outdated.
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 1 non-blocking finding
I read every changed file, ran the full test suite (48 provisioning-wizard tests pass; the unrelated re-export-identity failure is a pre-existing build-step issue), and verified TypeScript (
tsc --noEmitclean). The PR cleanly replaces the flat<select>+ModelOption[]API with the richerModelPicker+ModelInfo[]wire-format integration, removes hardcoded default models, adds a usefultriggerClassNameprop, and updates tests to cover grouping, search, auto-selection, and empty-list disabled states. No stale references to the old API remain in the repo. | Read all 6 changed files in
🟠 MEDIUM Deploy button doesn't validate non-empty modelTier — src/pages/provisioning-wizard.tsx
handleDeploy (line 581) sends
modelTierto the API without checking it's non-empty. Previously the default was always 'claude-sonnet' (via DEFAULT_MODEL_TIER at old line 138); now it defaults to '' (line 511). The ModelPicker trigger disables when models aren't loaded, so exploitation is unlikely, but a consumer passingdefaultConfig={{}}withoutmodelscould submit an empty modelTier. Consid
tangletools · 2026-05-07T02:37:02Z · trace
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 2 non-blocking findings
I read every changed file and ran the test suite. The PR cleanly replaces the flat
<select>model picker with the richerModelPickercomponent, removes the deprecatedModelOptionAPI, adds atriggerClassNameprop for styling flexibility, and correctly gates deployment onmodelTierbeing set (with an exception for bare mode). All 50 provisioning-wizard tests pass. The only test failure (re-export-identity.test.ts) is a pre-existing environment issue unrelated to this PR. No runtime defects were found. | Reviewed all 6 changed files. The PR replaces the flat HTML with ModelP 🟡 LOW triggerClassName and className overlap on same element — src/dashboard/model-picker.tsx Lines 262 and 283 merge both className and triggerClassName onto the trigger button. The JSDoc (lines 83-88) claims className styles the outer wrapper on the 'field' variant, but in the pill variant both land on the same <button>. This is harmless (cn() deduplicates) but the API contract is slightly misleading. Consider removing className from the trigger in the pill variant, or clarifying the JSDoc. 🟡 LOW ModelPicker loading prop is redundant with models array — src/pages/provisioning-wizard.tsx Line 1100 passes loading={!models} while also passing models={models ?? []}. The ModelPicker component treats loading and empty models identically in its empty-state copy ('Loading models...' vs 'No models available.'). This is fine but the prop combo is slightly redundant — the picker could infer loading from models.length === 0 && disabled. tangletools · 2026-05-07T03:02:33Z · trace
This comment was marked as outdated.
This comment was marked as outdated.
🤖 AI Code ReviewSummaryThe PR replaces the wizard's flat HTML select with the existing ModelPicker component, swapping the modelOptions: ModelOption[] prop for models: ModelInfo[] (router wire format), adding a triggerClassName prop to ModelPicker, removing the fictitious modelTier from template presets, and gating the deploy button on a non-empty modelTier unless bare mode is active. The changes are internally consistent, no other files consume the removed ModelOption type, and test coverage is thorough. No Issues Found✅ APPROVENo P1 issues and fewer than 3 P2 issues. The ModelPicker integration handles loading, empty-list, and bare-mode edge cases correctly; the breaking ModelOption removal has zero internal consumers; the deploy-button gating logic is sound; and the new tests cover the critical regressions (empty tier block, bare-mode bypass, auto-select, search filtering). Reviewed by Sokuza AI (zai-coding-plan/glm-5.1, via opencode) |
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 3 non-blocking findings
I read every changed file, ran the full test suite (221 tests passed, 1 build-order failure unrelated to this PR), and verified TypeScript compilation. The PR cleanly replaces the flat HTML
<select>with the richModelPickercomponent, removes the fictitiousmodelTierdefaults from templates, and adds a correct deploy gate that blocks submission while models are loading but allows bare-mode deploys. The logic in the auto-select effect and the reset paths is sound and well covered by new tests. | Read all 7 changed files in full, ran tests (50/50 wizard tests passed, typecheck clean), ver
🟡 LOW CHANGELOG omits TemplatePreset type-level breaking change — CHANGELOG.md
Line 3-5: the changelog documents the removal of
ModelOptionand themodelOptionsprop, but does not mention thatTemplatePresetno longer containsmodelTier(and thatgetPresetForTemplatereturn values have changed accordingly). Consumers who type againstTemplatePresetdirectly will see a compile-time break. Fix: add a one-line note thatTemplatePresetnow omitsmodelTierso templates delegate model selection to the wizard's auto-select.
🟡 LOW Test no longer exercises handleDeploy early-return path — src/pages/provisioning-wizard.test.tsx
Lines ~192-201: the test 'does not call onSubmit when onSubmit prop is undefined' renders
<ProvisioningWizard variant="flat" />without amodelsprop. Because the new deploy gate disables the button whenmodelTier=""andbare=false, this test now clicks a disabled button and never reacheshandleDeploy. It still passes, but it no longer verifies the early-return safety path it claims to cover. Fix: passmodels={TEST_MODELS}so the button is enabled and the early-return path is actually exercised.
🟡 LOW Dead loading state in ModelPicker integration — src/pages/provisioning-wizard.tsx
At line 1095–1103,
loading={!models}is always accompanied bydisabled={!models || models.length === 0}, so the trigger is always disabled when loading is true. Theloadingprop renders a spinner inside the dropdown (model-picker.tsx:325) and shows 'Loading models...' text (line 376), but the dropdown can never be opened when the trigger is disabled. The props are correct — no user-visible defect — butloadingis effectively dead code that communicates no additional state to the user. Consider removingloadinga
tangletools · 2026-05-07T03:26:58Z · trace
Summary
<select>in the provisioning wizard's AI agent step with the existingModelPicker, unlocking search, provider grouping, presets (Fast/Balanced/Best), pricing, and context-length display.models?: ModelInfo[]prop that takes priority over the legacymodelOptionsand accepts the rich Tangle Router/v1/modelspayload directly.triggerClassNameprop onModelPickerso callers can align the trigger with surrounding form fields without affecting the chat-input pill variant.Resolves the UX described in tangle-network/agent-dev-container#1060.