Skip to content

fix(wizard): use ModelPicker for model selection#38

Merged
Tjemmmic merged 4 commits intomainfrom
fix/wizard-model-picker
May 7, 2026
Merged

fix(wizard): use ModelPicker for model selection#38
Tjemmmic merged 4 commits intomainfrom
fix/wizard-model-picker

Conversation

@Tjemmmic
Copy link
Copy Markdown
Contributor

@Tjemmmic Tjemmmic commented May 7, 2026

Summary

  • Replaces the flat HTML <select> in the provisioning wizard's AI agent step with the existing ModelPicker, unlocking search, provider grouping, presets (Fast/Balanced/Best), pricing, and context-length display.
  • Adds a models?: ModelInfo[] prop that takes priority over the legacy modelOptions and accepts the rich Tangle Router /v1/models payload directly.
  • Adds an additive triggerClassName prop on ModelPicker so 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.

@tangletools
Copy link
Copy Markdown

tangletools commented May 7, 2026

✅ No Blockers — b4b468f0

Readiness 92/100 · Confidence 95/100 · 3 findings (3 low)

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 rich ModelPicker component, removes the fictitious modelTier defaults 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 ModelOption and the modelOptions prop, but does not mention that TemplatePreset no longer contains modelTier (and that getPresetForTemplate return values have changed accordingly). Consumers who type against TemplatePreset directly will see a compile-time break. Fix: add a one-line note that TemplatePreset now omits modelTier so 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 a models prop. Because the new deploy gate disables the button when modelTier="" and bare=false, this test now clicks a disabled button and never reaches handleDeploy. It still passes, but it no longer verifies the early-return safety path it claims to cover. Fix: pass models={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 by disabled={!models || models.length === 0}, so the trigger is always disabled when loading is true. The loading prop 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 — but loading is effectively dead code that communicates no additional state to the user. Consider removing loading a


tangletools · 2026-05-07T03:26:58Z · trace

@Tjemmmic

This comment was marked as outdated.

tangletools
tangletools previously approved these changes May 7, 2026
Copy link
Copy Markdown

@tangletools tangletools left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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. Because pickerModels prioritizes models when both props are passed, a caller could supply a populated models array alongside an all-disabled modelOptions fallback and the banner would still appear—even though the picker is fully functional. Fix: gate the banner on pickerModels.length === 0 or add !models to 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 raw modelOptions prop. When both models and modelOptions are passed (with modelOptions entries all disabled), pickerModels uses models so 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 on pickerModels.length === 0 && modelOptions && modelOptions.length > 0 && modelOptions.every(o => o.disabled), or suppress it entirely when models is 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 loading prop. When a caller passes models={[]} 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.
@Tjemmmic

This comment was marked as outdated.

tangletools
tangletools previously approved these changes May 7, 2026
Copy link
Copy Markdown

@tangletools tangletools left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 ./pages subpath export, and a behavioral regression where omitting models submits an empty modelTier instead of the previous hardcoded default.

🟠 MEDIUM Breaking public API: ModelOption and modelOptions removed — src/pages/index.ts

The ModelOption interface and modelOptions prop are removed from ProvisioningWizardProps, and ModelOption is dropped from the src/pages/index.ts barrel export. Because package.json exports "./pages" as a public subpath (@tangle-network/sandbox-ui/pages), this is a breaking change for any external consumer importing ModelOption or passing modelOptions. The PR replaces it with ModelInfo and models, 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 ?? "");. When models is omitted and defaultConfig has no modelTier, state initializes to "". The auto-correct effect (lines 521-527) returns early because models is undefined, so modelTier stays empty. handleDeploy (line 594) submits "" to onSubmit. The old code guaranteed a non-empty fallback (`DEFAUL


tangletools · 2026-05-07T02:19:14Z · trace

@Tjemmmic

This comment was marked as outdated.

tangletools
tangletools previously approved these changes May 7, 2026
Copy link
Copy Markdown

@tangletools tangletools left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 --noEmit clean). The PR cleanly replaces the flat <select> + ModelOption[] API with the richer ModelPicker + ModelInfo[] wire-format integration, removes hardcoded default models, adds a useful triggerClassName prop, 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 modelTier to 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 passing defaultConfig={{}} without models could submit an empty modelTier. Consid


tangletools · 2026-05-07T02:37:02Z · trace

tangletools
tangletools previously approved these changes May 7, 2026
Copy link
Copy Markdown

@tangletools tangletools left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 richer ModelPicker component, removes the deprecated ModelOption API, adds a triggerClassName prop for styling flexibility, and correctly gates deployment on modelTier being 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

@Tjemmmic

This comment was marked as outdated.

@Tjemmmic
Copy link
Copy Markdown
Contributor Author

Tjemmmic commented May 7, 2026

🤖 AI Code Review

Summary

The 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

✅ APPROVE

No 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)

Copy link
Copy Markdown

@tangletools tangletools left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 rich ModelPicker component, removes the fictitious modelTier defaults 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 ModelOption and the modelOptions prop, but does not mention that TemplatePreset no longer contains modelTier (and that getPresetForTemplate return values have changed accordingly). Consumers who type against TemplatePreset directly will see a compile-time break. Fix: add a one-line note that TemplatePreset now omits modelTier so 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 a models prop. Because the new deploy gate disables the button when modelTier="" and bare=false, this test now clicks a disabled button and never reaches handleDeploy. It still passes, but it no longer verifies the early-return safety path it claims to cover. Fix: pass models={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 by disabled={!models || models.length === 0}, so the trigger is always disabled when loading is true. The loading prop 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 — but loading is effectively dead code that communicates no additional state to the user. Consider removing loading a


tangletools · 2026-05-07T03:26:58Z · trace

@Tjemmmic Tjemmmic merged commit 8cf8206 into main May 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants