fix(FN-2498): keep New Task more options collapsed by default
- Add an autoExpandMoreOptionsOnSelection prop to TaskForm and gate advanced-section auto-expansion behind it - Disable auto-expansion in NewTaskModal so default-on workflow step preselection does not open More options on first render - Mark collapsed advanced content with the hidden attribute to prevent interaction until expanded - Add dashboard tests for default collapsed state, opt-out auto-expand behavior, and default-on workflow-step regression coverage - Replace hardcoded spacing/transition values in NewTaskModal.css with design tokens
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.new-task-modal .modal-body {
|
.new-task-modal .modal-body {
|
||||||
padding: 20px 24px;
|
padding: var(--space-xl);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: calc(80vh - 120px);
|
max-height: calc(80vh - 120px);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
.new-task-modal .form-group {
|
.new-task-modal .form-group {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 20px;
|
margin-bottom: var(--space-xl);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.new-task-modal .checkbox-label {
|
.new-task-modal .checkbox-label {
|
||||||
margin-bottom: 4px !important;
|
margin-bottom: var(--space-xs) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-form-primary-section {
|
.task-form-primary-section {
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding: 6px;
|
padding: var(--space-sm);
|
||||||
border: 1px solid var(--border-subtle);
|
border: 1px solid var(--border-subtle);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
padding: 10px 2px;
|
padding: var(--space-sm) var(--space-xs);
|
||||||
margin: 0 0 var(--space-sm);
|
margin: 0 0 var(--space-sm);
|
||||||
border: none;
|
border: none;
|
||||||
border-top: 1px solid var(--border-subtle);
|
border-top: 1px solid var(--border-subtle);
|
||||||
@@ -118,18 +118,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-form-more-options {
|
.task-form-more-options {
|
||||||
margin-left: 6px;
|
margin-left: var(--space-sm);
|
||||||
padding-left: 12px;
|
padding-left: var(--space-md);
|
||||||
border-left: 1px solid var(--border-subtle);
|
border-left: 1px solid var(--border-subtle);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 2400px;
|
max-height: 2400px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition:
|
transition:
|
||||||
max-height 0.24s ease,
|
max-height var(--transition-slow),
|
||||||
opacity 0.2s ease,
|
opacity var(--transition-normal),
|
||||||
margin-top 0.2s ease,
|
margin-top var(--transition-normal),
|
||||||
padding 0.2s ease,
|
padding var(--transition-normal),
|
||||||
border-color 0.2s ease;
|
border-color var(--transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-form-more-options.collapsed {
|
.task-form-more-options.collapsed {
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-form-more-options .form-group small {
|
.task-form-more-options .form-group small {
|
||||||
margin-top: 6px;
|
margin-top: var(--space-sm);
|
||||||
color: var(--text-secondary, var(--text-muted));
|
color: var(--text-secondary, var(--text-muted));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@
|
|||||||
.workflow-step-order-actions {
|
.workflow-step-order-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2px;
|
gap: var(--space-xs);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -455,6 +455,7 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
|
|||||||
onPriorityChange={setPriority}
|
onPriorityChange={setPriority}
|
||||||
renderBelowPrimary={quickFields}
|
renderBelowPrimary={quickFields}
|
||||||
hideDependencies={true}
|
hideDependencies={true}
|
||||||
|
autoExpandMoreOptionsOnSelection={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ export interface TaskFormProps {
|
|||||||
renderBelowPrimary?: React.ReactNode;
|
renderBelowPrimary?: React.ReactNode;
|
||||||
/** When true, skip rendering the Dependencies form-group inside "More options". Use when the parent renders its own dependency UI via renderBelowPrimary. */
|
/** When true, skip rendering the Dependencies form-group inside "More options". Use when the parent renders its own dependency UI via renderBelowPrimary. */
|
||||||
hideDependencies?: boolean;
|
hideDependencies?: boolean;
|
||||||
|
/** When true (default), More options auto-expands when non-default advanced selections are present. */
|
||||||
|
autoExpandMoreOptionsOnSelection?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TaskForm({
|
export function TaskForm({
|
||||||
@@ -128,6 +130,7 @@ export function TaskForm({
|
|||||||
onClose,
|
onClose,
|
||||||
renderBelowPrimary,
|
renderBelowPrimary,
|
||||||
hideDependencies,
|
hideDependencies,
|
||||||
|
autoExpandMoreOptionsOnSelection = true,
|
||||||
reviewLevel,
|
reviewLevel,
|
||||||
onReviewLevelChange,
|
onReviewLevelChange,
|
||||||
}: TaskFormProps) {
|
}: TaskFormProps) {
|
||||||
@@ -144,7 +147,9 @@ export function TaskForm({
|
|||||||
reviewLevel !== undefined;
|
reviewLevel !== undefined;
|
||||||
|
|
||||||
const [showDepDropdown, setShowDepDropdown] = useState(false);
|
const [showDepDropdown, setShowDepDropdown] = useState(false);
|
||||||
const [showMoreOptions, setShowMoreOptions] = useState(hasInitialMoreOptions);
|
const [showMoreOptions, setShowMoreOptions] = useState(
|
||||||
|
autoExpandMoreOptionsOnSelection ? hasInitialMoreOptions : false,
|
||||||
|
);
|
||||||
const [depSearch, setDepSearch] = useState("");
|
const [depSearch, setDepSearch] = useState("");
|
||||||
const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);
|
const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);
|
||||||
const [favoriteProviders, setFavoriteProviders] = useState<string[]>([]);
|
const [favoriteProviders, setFavoriteProviders] = useState<string[]>([]);
|
||||||
@@ -243,11 +248,16 @@ export function TaskForm({
|
|||||||
|
|
||||||
// Auto-expand advanced options when non-default values are present.
|
// Auto-expand advanced options when non-default values are present.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!autoExpandMoreOptionsOnSelection) {
|
||||||
|
hadMoreOptionSelectionsRef.current = hasMoreOptionSelections;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasMoreOptionSelections && !hadMoreOptionSelectionsRef.current) {
|
if (hasMoreOptionSelections && !hadMoreOptionSelectionsRef.current) {
|
||||||
setShowMoreOptions(true);
|
setShowMoreOptions(true);
|
||||||
}
|
}
|
||||||
hadMoreOptionSelectionsRef.current = hasMoreOptionSelections;
|
hadMoreOptionSelectionsRef.current = hasMoreOptionSelections;
|
||||||
}, [hasMoreOptionSelections]);
|
}, [hasMoreOptionSelections, autoExpandMoreOptionsOnSelection]);
|
||||||
|
|
||||||
// Keep dependency dropdown state clean when advanced options are collapsed.
|
// Keep dependency dropdown state clean when advanced options are collapsed.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -731,6 +741,7 @@ export function TaskForm({
|
|||||||
id="task-form-more-options"
|
id="task-form-more-options"
|
||||||
className={`task-form-more-options${showMoreOptions ? "" : " collapsed"}`}
|
className={`task-form-more-options${showMoreOptions ? "" : " collapsed"}`}
|
||||||
aria-hidden={!showMoreOptions}
|
aria-hidden={!showMoreOptions}
|
||||||
|
hidden={!showMoreOptions}
|
||||||
data-testid="task-form-more-options"
|
data-testid="task-form-more-options"
|
||||||
>
|
>
|
||||||
{/* Attachments */}
|
{/* Attachments */}
|
||||||
|
|||||||
@@ -97,7 +97,9 @@ describe("NewTaskModal", () => {
|
|||||||
renderNewTaskModal();
|
renderNewTaskModal();
|
||||||
|
|
||||||
const toggle = screen.getByTestId("task-form-more-options-toggle");
|
const toggle = screen.getByTestId("task-form-more-options-toggle");
|
||||||
|
const moreOptions = screen.getByTestId("task-form-more-options");
|
||||||
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
||||||
|
expect(moreOptions).toHaveAttribute("hidden");
|
||||||
// Dependencies are now in quick-fields (visible by default), so the dep-trigger is present
|
// Dependencies are now in quick-fields (visible by default), so the dep-trigger is present
|
||||||
expect(screen.getByTestId("dep-trigger")).toBeInTheDocument();
|
expect(screen.getByTestId("dep-trigger")).toBeInTheDocument();
|
||||||
|
|
||||||
@@ -105,6 +107,7 @@ describe("NewTaskModal", () => {
|
|||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
||||||
|
expect(moreOptions).not.toHaveAttribute("hidden");
|
||||||
});
|
});
|
||||||
// Model Configuration, Attachments, and Workflow Steps are revealed
|
// Model Configuration, Attachments, and Workflow Steps are revealed
|
||||||
expect(screen.getByText(/Model Configuration/i)).toBeTruthy();
|
expect(screen.getByText(/Model Configuration/i)).toBeTruthy();
|
||||||
@@ -536,6 +539,32 @@ describe("NewTaskModal", () => {
|
|||||||
|
|
||||||
// DefaultOn workflow step handling (FN-883)
|
// DefaultOn workflow step handling (FN-883)
|
||||||
describe("defaultOn workflow step handling", () => {
|
describe("defaultOn workflow step handling", () => {
|
||||||
|
it("keeps More options collapsed by default even when defaultOn workflow steps are auto-applied", async () => {
|
||||||
|
const { fetchWorkflowSteps } = await import("../../api");
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
|
||||||
|
{ id: "WS-001", name: "QA Check", description: "Run tests", prompt: "Check tests", enabled: true, defaultOn: true, createdAt: "", updatedAt: "" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
renderNewTaskModal();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("workflow-step-checkbox-WS-001")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggle = screen.getByTestId("task-form-more-options-toggle");
|
||||||
|
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
||||||
|
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText(/Model Configuration/i)).toBeTruthy();
|
||||||
|
expect(screen.getByText(/Attachments/i)).toBeTruthy();
|
||||||
|
expect(screen.getByText(/Workflow Steps/i)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it("sends undefined enabledWorkflowSteps when no defaultOn steps and user hasn't interacted", async () => {
|
it("sends undefined enabledWorkflowSteps when no defaultOn steps and user hasn't interacted", async () => {
|
||||||
const { fetchWorkflowSteps } = await import("../../api");
|
const { fetchWorkflowSteps } = await import("../../api");
|
||||||
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
|
||||||
|
|||||||
@@ -1116,6 +1116,42 @@ describe("TaskForm defaultOn auto-selection (FN-883)", () => {
|
|||||||
expect(onDefaultOnApplied).toHaveBeenCalledWith(["WS-001"]);
|
expect(onDefaultOnApplied).toHaveBeenCalledWith(["WS-001"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("auto-expands More options by default when advanced selections are prefilled", () => {
|
||||||
|
renderTaskForm({
|
||||||
|
mode: "create",
|
||||||
|
selectedWorkflowSteps: ["WS-001"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId("task-form-more-options-toggle")).toHaveAttribute("aria-expanded", "true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stays collapsed when auto-expand is disabled even with prefilled advanced selections", () => {
|
||||||
|
renderTaskForm({
|
||||||
|
mode: "create",
|
||||||
|
selectedWorkflowSteps: ["WS-001"],
|
||||||
|
autoExpandMoreOptionsOnSelection: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId("task-form-more-options-toggle")).toHaveAttribute("aria-expanded", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hides advanced controls from interaction when More options is collapsed", () => {
|
||||||
|
renderTaskForm({ mode: "create" });
|
||||||
|
|
||||||
|
const toggle = screen.getByTestId("task-form-more-options-toggle");
|
||||||
|
const moreOptions = screen.getByTestId("task-form-more-options");
|
||||||
|
|
||||||
|
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
||||||
|
expect(moreOptions).toHaveAttribute("hidden");
|
||||||
|
expect(moreOptions).toHaveAttribute("aria-hidden", "true");
|
||||||
|
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
|
||||||
|
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
||||||
|
expect(moreOptions).not.toHaveAttribute("hidden");
|
||||||
|
expect(moreOptions).toHaveAttribute("aria-hidden", "false");
|
||||||
|
});
|
||||||
|
|
||||||
it("does not auto-select workflow steps in edit mode", async () => {
|
it("does not auto-select workflow steps in edit mode", async () => {
|
||||||
const { fetchWorkflowSteps } = await import("../../api");
|
const { fetchWorkflowSteps } = await import("../../api");
|
||||||
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
|
||||||
|
|||||||
Reference in New Issue
Block a user