docs(FN-808): document nested model menu and add regression tests

- Document nested model menu behavior in QuickEntryBox in AGENTS.md
- Add README section describing the model selection nested menu pattern
- Add regression tests for nested model menu submenu open/close behavior
This commit is contained in:
gsxdsm
2026-04-04 09:10:28 -07:00
parent 5e18ca1b0e
commit 2e629a6666
3 changed files with 102 additions and 1 deletions

View File

@@ -585,7 +585,7 @@ Both components provide the same task creation experience with the following opt
- **Description input** — Type the task description. Press Enter to create immediately, or use the action buttons for AI-assisted creation.
- **Deps button** — Add task dependencies before creation.
- **Models button** — Override the default AI models for this task (executor and validator).
- **Models button** — Opens a nested menu with Plan, Executor, and Validator roles. Each role opens a submenu with a model dropdown for per-task overrides.
- **Plan button** (Lightbulb icon) — Opens the AI Planning Mode modal with the current description pre-filled. This allows refining the task through an interactive Q&A before creation.
- **Subtask button** (ListTree icon) — Opens the subtask breakdown dialog with the current description pre-filled. The dialog generates 25 AI-suggested subtasks, lets the user edit titles, descriptions, sizes, and dependencies, and then creates all subtasks in one action.

View File

@@ -339,6 +339,18 @@ When creating tasks, the AI can refine your description:
- Suggests appropriate file scopes and steps
- Available in both Quick Entry and planning mode
### Quick Entry Model Selection
The quick entry box provides a **Models** button (🧠) that opens a nested menu for overriding AI models on a per-task basis:
1. Click the **Models** button (🧠) to open the top-level menu
2. Choose a role: **Plan**, **Executor**, or **Validator**
3. Select a specific model from the dropdown submenu
4. Press **Back** or Escape to return to the role selector
5. The menu shows "Using default" for roles without an override and highlights roles with a custom selection
The selected executor and validator models are included in the task creation payload. Planning model overrides apply only during task specification (triage).
### Manual Plan Approval
Enable `requirePlanApproval` in settings for manual review of AI-generated specifications:

View File

@@ -646,6 +646,95 @@ describe("QuickEntryBox", () => {
expect(screen.getByTestId("model-menu-validator")).toBeTruthy();
});
it("Escape from submenu returns to top-level menu without closing it", () => {
renderQuickEntryBox({});
expandQuickEntry();
const textarea = screen.getByTestId("quick-entry-input");
fireEvent.change(textarea, { target: { value: "Task with models" } });
fireEvent.click(screen.getByTestId("quick-entry-models-button"));
fireEvent.click(screen.getByTestId("model-menu-executor"));
// Should be in submenu — back button visible
expect(screen.getByTestId("model-submenu-back")).toBeTruthy();
// Press Escape — should go back to top-level, not close the entire menu
fireEvent.keyDown(textarea, { key: "Escape" });
// Top-level menu items should be visible again
expect(screen.getByTestId("model-menu-plan")).toBeTruthy();
expect(screen.getByTestId("model-menu-executor")).toBeTruthy();
expect(screen.getByTestId("model-menu-validator")).toBeTruthy();
// Submenu should not be visible
expect(screen.queryByTestId("model-submenu-back")).toBeNull();
});
it("selecting Plan model updates the Plan menu item value", () => {
renderQuickEntryBox({});
expandQuickEntry();
const textarea = screen.getByTestId("quick-entry-input");
fireEvent.change(textarea, { target: { value: "Task with models" } });
fireEvent.click(screen.getByTestId("quick-entry-models-button"));
fireEvent.click(screen.getByTestId("model-menu-plan"));
// Select a model via mocked dropdown
fireEvent.click(screen.getByTestId("dropdown-select-plan model"));
// Go back to top-level menu
fireEvent.click(screen.getByTestId("model-submenu-back"));
// Plan menu item should show the selected model, not "Using default"
const planItem = screen.getByTestId("model-menu-plan");
expect(planItem.textContent).toContain("anthropic/claude-sonnet-4-5");
expect(planItem.textContent).not.toContain("Using default");
// Should have active class
expect(planItem.classList.contains("model-menu-item--active")).toBe(true);
});
it("selecting Validator model updates the Validator menu item value", () => {
renderQuickEntryBox({});
expandQuickEntry();
const textarea = screen.getByTestId("quick-entry-input");
fireEvent.change(textarea, { target: { value: "Task with models" } });
fireEvent.click(screen.getByTestId("quick-entry-models-button"));
fireEvent.click(screen.getByTestId("model-menu-validator"));
// Select a model via mocked dropdown
fireEvent.click(screen.getByTestId("dropdown-select-validator model"));
// Go back to top-level menu
fireEvent.click(screen.getByTestId("model-submenu-back"));
// Validator menu item should show the selected model
const validatorItem = screen.getByTestId("model-menu-validator");
expect(validatorItem.textContent).toContain("anthropic/claude-sonnet-4-5");
expect(validatorItem.classList.contains("model-menu-item--active")).toBe(true);
});
it("clearing Plan model returns menu item to default state", () => {
renderQuickEntryBox({});
expandQuickEntry();
const textarea = screen.getByTestId("quick-entry-input");
fireEvent.change(textarea, { target: { value: "Task with models" } });
fireEvent.click(screen.getByTestId("quick-entry-models-button"));
fireEvent.click(screen.getByTestId("model-menu-plan"));
// Select then clear model
fireEvent.click(screen.getByTestId("dropdown-select-plan model"));
fireEvent.click(screen.getByTestId("dropdown-clear-plan model"));
// Go back to top-level menu
fireEvent.click(screen.getByTestId("model-submenu-back"));
// Plan menu item should show "Using default" and no active class
const planItem = screen.getByTestId("model-menu-plan");
expect(planItem.textContent).toContain("Using default");
expect(planItem.classList.contains("model-menu-item--active")).toBe(false);
});
it("selects dependencies and includes them in submit payload", async () => {
const { props } = renderQuickEntryBox({});
expandQuickEntry();