Skip to content

Commit ded1041

Browse files
Im An AIclauderyaneggz
authored
FROM feat/772-schedule-model-dropdown TO development (#780)
* docs: add PRD for issue #772 — schedule model dropdown * docs: add PRD and ralph prd.json for issue #772 schedule model dropdown * chore: remove misplaced agents/prd.json * feat: US-001 - Decouple SelectModel from ChatContext Add optional controlled props (value, onChange, modelsList) to SelectModel so it can be used outside of ChatContext. Falls back to useChatContext() when props are not provided, maintaining backward compatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: im-an-ai-agent <im.an.ai.agent@gmail.com> * feat: US-002 - Add standalone models list fetcher Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: im-an-ai-agent <im.an.ai.agent@gmail.com> * docs: update PRD and progress for US-002 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: im-an-ai-agent <im.an.ai.agent@gmail.com> * feat: US-003 - Replace text input with SelectModel in AgentScheduleForm Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: im-an-ai-agent <im.an.ai.agent@gmail.com> * docs: update PRD and progress for US-003 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: im-an-ai-agent <im.an.ai.agent@gmail.com> * feat: US-004 - Verify schedule edit loads saved model in dropdown Fix inheritFromAgent defaulting to true when editing a schedule saved with custom configuration. Changed || true to ?? true so false values from saved schedules are preserved correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: im-an-ai-agent <im.an.ai.agent@gmail.com> * docs: update PRD and progress for US-004 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: im-an-ai-agent <im.an.ai.agent@gmail.com> * chore: finished iteration 1 * Select field is in place --------- Signed-off-by: im-an-ai-agent <im.an.ai.agent@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: ryaneggz <kre8mymedia@gmail.com>
1 parent 59e8a02 commit ded1041

9 files changed

Lines changed: 284 additions & 251 deletions

File tree

.claude/plans/feat-772/01.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Fix SelectModel Dropdown Width & Scroll in Schedule Form
2+
3+
## Context
4+
On the Schedules page (`/schedules`), when editing a schedule with "Custom Configuration" mode, the model dropdown (SelectModel component) has two visual bugs:
5+
1. **Width mismatch** — The dropdown popover is hardcoded to `w-[300px]` (300px), while the trigger button is `w-full` (responsive to its grid column). This causes the dropdown to be narrower than the trigger.
6+
2. **Scroll not working** — The dropdown list extends beyond its container without a visible/functional scrollbar.
7+
8+
## Root Cause
9+
10+
**Width**: `SelectModel.tsx:128` sets `<PopoverContent className="w-[300px] p-0">` — a fixed width that doesn't match the trigger's responsive width.
11+
12+
**Scroll**: The `CommandList` in `command.tsx:61` uses `max-h-[300px] overflow-y-auto overflow-x-hidden`. The `overflow-y-auto` with `max-h` should enable scrolling, but the Radix Popover portal + `overflow-hidden` on the Command root (`command.tsx:16`) may be interfering. Additionally, the `CommandGroup` at `command.tsx:88` has `overflow-hidden` which could clip content.
13+
14+
## Changes
15+
16+
### 1. Match dropdown width to trigger width
17+
**File**: `frontend/src/components/lists/SelectModel.tsx` (line 128)
18+
19+
Change:
20+
```tsx
21+
<PopoverContent className="w-[300px] p-0">
22+
```
23+
To:
24+
```tsx
25+
<PopoverContent className="w-[--radix-popover-trigger-width] p-0">
26+
```
27+
28+
Radix UI Popover exposes `--radix-popover-trigger-width` as a CSS custom property on the content element, allowing the dropdown to always match the trigger's width.
29+
30+
### 2. Ensure scroll works reliably
31+
**File**: `frontend/src/components/lists/SelectModel.tsx` (line 131)
32+
33+
Add an explicit `className` to `CommandList` to ensure scroll behavior:
34+
```tsx
35+
<CommandList className="max-h-[300px] overflow-y-auto">
36+
```
37+
38+
This reinforces the scroll constraints directly on the SelectModel's CommandList instance, ensuring the dropdown is scrollable regardless of parent overflow settings.
39+
40+
## Files to Modify
41+
- `frontend/src/components/lists/SelectModel.tsx` — PopoverContent width class + CommandList scroll class
42+
43+
## Verification
44+
1. Run `npm run dev` from `frontend/`
45+
2. Navigate to `/schedules`, edit/create a schedule
46+
3. Select "Custom Configuration" mode
47+
4. Click the Model dropdown and verify:
48+
- Dropdown width matches the trigger button width
49+
- Scrollbar appears and works when model list exceeds viewport
50+
- Model selection still works correctly
51+
5. Also verify the SelectModel in the chat sidebar still works correctly (uses same component without controlled props)
52+
6. Run `npm run test` to ensure no regressions

.ralph/prd.json

Lines changed: 34 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,68 @@
11
{
22
"project": "Orchestra",
3-
"branchName": "feat/769-deep-agent-backend-sandbox-patterns",
4-
"description": "Extensible Sandbox/Backend Selection - Re-introduce user-configurable sandbox selection using enum + registry dict + factory function pattern so adding new backends requires ~3 lines of code",
3+
"branchName": "feat/772-schedule-model-dropdown",
4+
"description": "Replace manual model text input with ModelSelect dropdown on schedule create/edit pages",
55
"userStories": [
66
{
77
"id": "US-001",
8-
"title": "Add SandboxType enum and schema fields",
9-
"description": "As a developer, I need a SandboxType enum and schema fields so the system has a typed, validated representation of sandbox choices.",
8+
"title": "Decouple SelectModel from ChatContext",
9+
"description": "As a developer, I need SelectModel to accept model state via props so it can be reused outside of ChatContext (e.g., in schedule forms).",
1010
"acceptanceCriteria": [
11-
"SandboxType(str, Enum) added to backend/src/schemas/entities/settings.py with values 'auto', 'daytona', 'state'",
12-
"default_sandbox: Optional[str] = None field added to UserSettings entity",
13-
"default_sandbox: Optional[str] = None field added to UserSettingsResponse model",
14-
"UpdateDefaultSandboxRequest(BaseModel) added with sandbox: Optional[str] field",
15-
"Typecheck passes (make format)"
11+
"SelectModel accepts optional props: value (string), onChange (callback), modelsList (array)",
12+
"When props are provided, SelectModel uses them instead of useChatContext()",
13+
"When props are NOT provided, SelectModel falls back to useChatContext() (backward compatible)",
14+
"Existing usages of SelectModel in chat pages continue to work unchanged",
15+
"Typecheck passes"
1616
],
1717
"priority": 1,
1818
"passes": true,
19-
"notes": ""
19+
"notes": "SelectModel currently imports useChatContext for model/setModel/models and useModelVisibility for filtering. Add optional controlled props with fallback to context."
2020
},
2121
{
2222
"id": "US-002",
23-
"title": "Add set_default_sandbox repo method with unit tests",
24-
"description": "As a developer, I need a repository method to persist the user's sandbox preference so it can be read back on every agent invocation.",
23+
"title": "Add standalone models list fetcher",
24+
"description": "As a developer, I need a way to fetch available models outside of ChatContext so the schedule form can populate the SelectModel dropdown.",
2525
"acceptanceCriteria": [
26-
"set_default_sandbox(sandbox) method added to UserSettingsRepo in backend/src/repos/user_settings_repo.py, mirroring set_default_model()",
27-
"Method validates input against SandboxType enum values; raises ValueError for invalid input",
28-
"None input clears the setting (same as clearing default model)",
29-
"Unit test added: test_set_default_sandbox stores and returns the value",
30-
"Unit test added: test_clear_default_sandbox null clears the value",
31-
"Unit test added: test_set_invalid_sandbox_raises rejects invalid values",
32-
"make test passes"
26+
"A hook or utility exists to fetch the models list from the /llm/models API endpoint",
27+
"Returns the same shape as ChatContext's models array",
28+
"Can be used independently of ChatContext",
29+
"Typecheck passes"
3330
],
3431
"priority": 2,
3532
"passes": true,
36-
"notes": ""
33+
"notes": "Check if ChatContext already fetches from an endpoint that can be extracted into a standalone hook. The /llm/models endpoint likely already exists."
3734
},
3835
{
3936
"id": "US-003",
40-
"title": "Add PUT /settings/default-sandbox endpoint with route tests",
41-
"description": "As a frontend client, I need an API endpoint to update the user's default sandbox setting.",
37+
"title": "Replace text input with SelectModel in AgentScheduleForm",
38+
"description": "As a user creating a schedule, I want to select a model from a dropdown instead of typing the model key name manually.",
4239
"acceptanceCriteria": [
43-
"PUT /settings/default-sandbox endpoint added to backend/src/routes/v0/settings.py, mirroring update_default_model",
44-
"Endpoint accepts UpdateDefaultSandboxRequest body",
45-
"ValueError from repo is caught and returns HTTP 400",
46-
"All existing settings endpoint responses updated to include default_sandbox field",
47-
"Route test added: test_get_settings_includes_default_sandbox verifies field in GET response",
48-
"Route test added: test_set_default_sandbox verifies PUT stores and returns value",
49-
"Route test added: test_set_invalid_sandbox_returns_400 verifies invalid input returns 400",
50-
"make test passes"
40+
"The Model field in AgentScheduleForm uses SelectModel component instead of text Input",
41+
"The dropdown shows the same models available in the chat model selector",
42+
"Selected model value is correctly written to the form state (customModel field)",
43+
"The agent's current model is shown as placeholder/default when inheriting",
44+
"Model selection persists correctly on form submit",
45+
"Typecheck passes",
46+
"Verify in browser using agent-browser skill"
5147
],
5248
"priority": 3,
5349
"passes": true,
54-
"notes": ""
50+
"notes": "File: frontend/src/components/forms/AgentScheduleForm.tsx. The Input at line ~506 with placeholder={agent.model} needs to become a controlled SelectModel. The form uses react-hook-form with register('customModel')."
5551
},
5652
{
5753
"id": "US-004",
58-
"title": "Implement registry pattern in resolve_sandbox_backend with dispatch tests",
59-
"description": "As a developer, I need resolve_sandbox_backend() to accept a sandbox_type parameter and dispatch to the correct factory, so the function is extensible without modifying its core logic.",
60-
"acceptanceCriteria": [
61-
"_create_daytona_backend_checked(runtime) factory extracted in backend/src/agents/__init__.py wrapping existing Daytona creation logic",
62-
"_create_state_backend(runtime) factory extracted creating StateBackend",
63-
"_SANDBOX_FACTORIES dict maps SandboxType values to factory callables",
64-
"resolve_sandbox_backend(runtime, sandbox_type=None) dispatches: None/'auto' tries Daytona then falls back to State; 'state' uses State directly; 'daytona' tries Daytona then falls back; unknown treats as auto",
65-
"Unit test added: test_auto_uses_daytona_when_available (sandbox_type=None uses Daytona)",
66-
"Unit test added: test_auto_falls_back_to_state (sandbox_type=None falls back when unavailable)",
67-
"Unit test added: test_explicit_state_skips_daytona (sandbox_type='state' never calls create_daytona_backend)",
68-
"Unit test added: test_explicit_daytona_falls_back_gracefully (sandbox_type='daytona' falls back if unavailable)",
69-
"make test passes"
70-
],
71-
"priority": 4,
72-
"passes": true,
73-
"notes": ""
74-
},
75-
{
76-
"id": "US-005",
77-
"title": "Wire sandbox_type through LLMController",
78-
"description": "As a user, I want my sandbox preference applied when I invoke or stream an agent, so my setting is respected in all execution paths.",
79-
"acceptanceCriteria": [
80-
"_resolve_user_settings() in backend/src/controllers/llm.py returns 3-tuple (model, api_key, default_sandbox) instead of 2-tuple",
81-
"default_sandbox read from the already-fetched settings object",
82-
"sandbox_type passed to resolve_sandbox_backend() in llm_invoke()",
83-
"sandbox_type passed through to stream_generator() in llm_stream()",
84-
"make test passes"
85-
],
86-
"priority": 5,
87-
"passes": true,
88-
"notes": ""
89-
},
90-
{
91-
"id": "US-006",
92-
"title": "Wire sandbox_type through stream_generator",
93-
"description": "As a developer, I need stream_generator() to accept and forward the sandbox preference so streaming execution uses the correct backend.",
94-
"acceptanceCriteria": [
95-
"sandbox_type: str | None = None parameter added to stream_generator() in backend/src/utils/stream.py",
96-
"Parameter passed to resolve_sandbox_backend(runtime, sandbox_type)",
97-
"make test passes"
98-
],
99-
"priority": 6,
100-
"passes": true,
101-
"notes": ""
102-
},
103-
{
104-
"id": "US-007",
105-
"title": "Wire sandbox_type through worker tasks",
106-
"description": "As a developer, I need _execute_agent_stream() in the distributed worker to read and forward the sandbox preference from user settings.",
54+
"title": "Verify schedule edit loads saved model in dropdown",
55+
"description": "As a user editing an existing schedule, I want the previously selected model to appear pre-selected in the dropdown.",
10756
"acceptanceCriteria": [
108-
"settings.default_sandbox read from the already-fetched settings object in _execute_agent_stream() in backend/src/workers/tasks.py",
109-
"Value passed to resolve_sandbox_backend(runtime, sandbox_type)",
110-
"make test passes"
111-
],
112-
"priority": 7,
113-
"passes": true,
114-
"notes": ""
115-
},
116-
{
117-
"id": "US-008",
118-
"title": "Frontend service types and API call",
119-
"description": "As a frontend developer, I need TypeScript types and an API function to interact with the new sandbox settings endpoint.",
120-
"acceptanceCriteria": [
121-
"SandboxType type exported in frontend/src/lib/services/userSettingsService.ts: 'auto' | 'daytona' | 'state'",
122-
"default_sandbox: string | null field added to UserSettingsResponse interface",
123-
"updateDefaultSandbox(sandbox: string | null) function added that PUTs to /settings/default-sandbox",
124-
"npm run test passes",
125-
"npm run build passes"
126-
],
127-
"priority": 8,
128-
"passes": true,
129-
"notes": ""
130-
},
131-
{
132-
"id": "US-009",
133-
"title": "SandboxSettings frontend component",
134-
"description": "As a user, I want a dropdown in the Settings page to choose my default sandbox backend.",
135-
"acceptanceCriteria": [
136-
"New file frontend/src/components/settings/SandboxSettings.tsx created",
137-
"Follows DefaultModelSettings.tsx pattern (Card + Select dropdown)",
138-
"3 options displayed: Auto (Recommended), Daytona, Local",
139-
"Current value fetched from getSettings() on mount",
140-
"Selection calls updateDefaultSandbox() and shows toast on success/error",
57+
"When editing a schedule, the SelectModel dropdown shows the saved model as selected",
58+
"Changing the model and saving persists the new selection",
59+
"Switching between Inherit from agent and custom correctly toggles the dropdown state",
14160
"Typecheck passes",
14261
"Verify in browser using agent-browser skill"
14362
],
144-
"priority": 9,
145-
"passes": true,
146-
"notes": ""
147-
},
148-
{
149-
"id": "US-010",
150-
"title": "Wire SandboxSettings into Settings page",
151-
"description": "As a user, I want the sandbox selector visible on the Settings page between existing settings sections.",
152-
"acceptanceCriteria": [
153-
"SandboxSettings imported in frontend/src/pages/settings/index.tsx",
154-
"Rendered between <DefaultModelSettings /> and <UserApiKeysSettings />",
155-
"npm run test passes",
156-
"npm run build passes",
157-
"Verify in browser using agent-browser skill"
158-
],
159-
"priority": 10,
63+
"priority": 4,
16064
"passes": true,
161-
"notes": ""
65+
"notes": "initialData?.customModel is set at line 64 of AgentScheduleForm.tsx. This value needs to be passed as the controlled value to SelectModel."
16266
}
16367
]
16468
}

0 commit comments

Comments
 (0)