From 8e1e0920dfdfc2913e94a076c9ffcbccb08db219 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 22 Jun 2026 09:58:18 -0700 Subject: [PATCH] feat(dashboard): New Task 'Advanced' disclosure + inline quick-add buttons; cap Memory editor height - New Task dialog: deep options (models, branch, node, review level, auto-merge, workflow, github tracking, attachments) collapse behind an 'Advanced' disclosure (collapsed by default); inline Attach/Fast/Priority quick-add buttons (QuickEntryBox style) sit next to Plan, wired to existing TaskForm state. - Memory view: cap the editor box to ~a page (max-height: 60vh) with internal CodeMirror scroll, so a long memory file no longer forces endless page scrolling. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../dashboard/app/components/MemoryView.css | 14 ++++ .../dashboard/app/components/NewTaskModal.tsx | 1 - .../dashboard/app/components/TaskForm.tsx | 79 +++++++++++++++++-- .../__tests__/NewTaskModal.test.tsx | 65 ++++++++++----- .../components/__tests__/TaskForm.test.tsx | 5 +- 5 files changed, 137 insertions(+), 27 deletions(-) diff --git a/packages/dashboard/app/components/MemoryView.css b/packages/dashboard/app/components/MemoryView.css index c01778ff4a..1eef76aada 100644 --- a/packages/dashboard/app/components/MemoryView.css +++ b/packages/dashboard/app/components/MemoryView.css @@ -113,6 +113,10 @@ Inside the working-tab editor section every block keeps its intrinsic height (fl min-height: 0; } +/* +FNXC:Memory 2026-06-23-00:20: +The memory editor box is CAPPED to about a page (max-height: 60vh) so a long memory file does not push the page into endless scrolling — the box itself stays bounded and the CodeMirror editor SCROLLS INTERNALLY. cm-editor fills the capped container (height:100%) and cm-scroller owns the vertical scroll. +*/ .memory-editor-container { border: 1px solid var(--border); border-radius: var(--radius-md); @@ -121,6 +125,16 @@ Inside the working-tab editor section every block keeps its intrinsic height (fl flex-direction: column; flex: 1 1 auto; min-height: calc(var(--space-xl) * 13 + var(--space-xs) * 2); + max-height: 60vh; +} + +.memory-editor-container .cm-editor { + height: 100%; + min-height: 0; +} + +.memory-editor-container .cm-scroller { + overflow: auto; } .memory-insights-editor-layout { diff --git a/packages/dashboard/app/components/NewTaskModal.tsx b/packages/dashboard/app/components/NewTaskModal.tsx index 7539040849..9378109744 100644 --- a/packages/dashboard/app/components/NewTaskModal.tsx +++ b/packages/dashboard/app/components/NewTaskModal.tsx @@ -850,7 +850,6 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask, renderBelowPrimary={quickFields} hideDependencies={true} autoExpandMoreOptionsOnSelection={false} - forceMoreOptionsOpen={true} /> diff --git a/packages/dashboard/app/components/TaskForm.tsx b/packages/dashboard/app/components/TaskForm.tsx index 56af7c4bc8..842c605f42 100644 --- a/packages/dashboard/app/components/TaskForm.tsx +++ b/packages/dashboard/app/components/TaskForm.tsx @@ -8,7 +8,7 @@ import { applyPresetToSelection, getRecommendedPresetForSize } from "../utils/mo import { CustomModelDropdown } from "./CustomModelDropdown"; import { NodeHealthDot } from "./NodeHealthDot"; import { LoadingSpinner } from "./LoadingSpinner"; -import { Sparkles, ChevronUp, ChevronDown, Maximize2, Minimize2 } from "lucide-react"; +import { Sparkles, ChevronUp, ChevronDown, Maximize2, Minimize2, Paperclip, Flag, Zap } from "lucide-react"; import { REPO_OVERRIDE_RE, resolveEffectiveGithubRepoDefault } from "./githubTracking"; function getNodeStatusLabel(status: NodeInfo["status"], t: (key: string, defaultValue: string) => string): string { @@ -148,7 +148,10 @@ export interface TaskFormProps { autoExpandMoreOptionsOnSelection?: boolean; /** * FNXC:NewTask 2026-06-22-20:30: - * When true, the advanced ("More options") controls are always shown — the collapsible disclosure is force-open and its toggle is hidden. The New Task dialog sets this so every quick-add control QuickEntryBox exposes (priority, execution-mode/Fast toggle, model selectors, attachments, node, GitHub tracking, etc.) is visible without a click. Other surfaces keep the default collapsed disclosure. + * When true, the advanced controls disclosure is always shown — the collapsible disclosure is force-open and its toggle is hidden. Other surfaces keep the default collapsed disclosure. + * + * FNXC:NewTask 2026-06-23-00:10: + * The New Task dialog NO LONGER forces this open. The deep/advanced options (model selectors, branch/base, node, review level, GitHub tracking, etc.) are collapsed by default behind the "Advanced" disclosure; only the common quick-add buttons (Attach, Fast, Priority) are surfaced inline next to Plan. This prop remains for any caller that still wants every advanced control un-collapsed. */ forceMoreOptionsOpen?: boolean; } @@ -837,8 +840,15 @@ export function TaskForm({ - {/* AI-assisted creation actions — adjacent to description (create mode only) */} - {mode === "create" && (onPlanningMode || onSubtaskBreakdown) && ( + {/* + FNXC:NewTask 2026-06-23-00:10: + Common quick-add action row, adjacent to the description (create mode only). The deep/advanced controls stay collapsed behind the "Advanced" disclosure, but the buttons users reach for most — Attach, Fast (execution-mode), Priority — are surfaced INLINE here next to Plan, styled identically to QuickEntryBox's quick-add buttons (shared `.btn .btn-sm`, `.dep-trigger`, lucide icons at size 12). They are wired to TaskForm's existing state/handlers, NOT duplicated: + - Attach → fileInputRef.click() (same hidden input the Advanced Attachments group uses; onImagesChange handles the file). + - Fast → toggles executionMode standard⇄fast via onExecutionModeChange (mirrors QuickEntryBox quick-entry-fast-toggle). + - Priority → cycles through TASK_PRIORITIES via onPriorityChange (Flag affordance). + Plan/Subtask remain gated on their handoff callbacks. Model selectors, branch/base, node, review level, and GitHub tracking stay in the Advanced disclosure. + */} + {mode === "create" && (
{onPlanningMode && ( + + {/* FNXC:NewTask 2026-06-23-00:10: Fast — toggles executionMode standard⇄fast; btn-primary when active, matching QuickEntryBox's fast toggle. */} + {onExecutionModeChange && executionMode !== undefined && ( + + )} + + {/* FNXC:NewTask 2026-06-23-00:10: Priority — cycles TASK_PRIORITIES via onPriorityChange (Flag affordance, same label shape as QuickEntryBox). */} + {onPriorityChange && ( + + )}
)} {renderBelowPrimary} - {/* FNXC:NewTask 2026-06-22-20:30: Hide the disclosure toggle entirely when force-open — there is nothing to collapse, so the New Task dialog shows every advanced control without a click. */} + {/* + FNXC:NewTask 2026-06-22-20:30: Hide the disclosure toggle entirely when force-open — there is nothing to collapse. + FNXC:NewTask 2026-06-23-00:10: The disclosure now reads "Advanced" (was "More options"). It stays collapsed by default and hides only the DEEP options (model selectors, branch/base, node, review level, GitHub tracking, workflow). The common quick-add buttons (Attach/Fast/Priority) live inline next to Plan and are always visible, so they are NOT buried behind this toggle. + */} {!forceMoreOptionsOpen && ( )} diff --git a/packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx b/packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx index 17457a03c6..4c496368d0 100644 --- a/packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx @@ -15,6 +15,9 @@ vi.mock("lucide-react", () => ({ Maximize2: () => null, Minimize2: () => null, Workflow: () => null, + Paperclip: () => null, + Flag: () => null, + Zap: () => null, })); // Mock the api module @@ -136,15 +139,27 @@ describe("NewTaskModal", () => { expect(screen.getByText("New Task")).toBeTruthy(); expect(screen.getByPlaceholderText("What needs to be done?")).toBeTruthy(); - expect(screen.queryByRole("button", { name: "Plan" })).toBeNull(); - expect(screen.queryByRole("button", { name: "Subtask" })).toBeNull(); - expect(screen.queryByTestId("task-form-description-actions")).toBeNull(); + // Without AI-handoff callbacks there is no Plan/Subtask button… + expect(screen.queryByTestId("task-form-plan-button")).toBeNull(); + expect(screen.queryByTestId("task-form-subtask-button")).toBeNull(); + // …but FNXC:NewTask 2026-06-23-00:10: the inline quick-add action row still renders in create mode to host Attach/Fast/Priority. + expect(screen.getByTestId("task-form-description-actions")).toBeInTheDocument(); // Dependencies and agent are in quick-fields — visible by default (no toggle needed) expect(screen.getByTestId("dep-trigger")).toBeInTheDocument(); expect(screen.getByTestId("new-task-agent-button")).toBeInTheDocument(); + // FNXC:NewTask 2026-06-23-00:10: The common quick-add buttons (Attach, Fast, Priority) are surfaced INLINE next to the actions row and visible immediately. + expect(screen.getByTestId("task-form-inline-attach")).toBeInTheDocument(); + expect(screen.getByTestId("task-form-inline-fast")).toBeInTheDocument(); + expect(screen.getByTestId("task-form-inline-priority")).toBeInTheDocument(); + // FNXC:NewTask 2026-06-23-00:10: The DEEP/advanced options now sit behind the collapsed "Advanced" disclosure. Model Configuration / Attachments are NOT shown until the toggle is expanded. + const advancedToggle = screen.getByTestId("task-form-more-options-toggle"); + expect(advancedToggle).toHaveTextContent(/Advanced/i); + expect(screen.getByTestId("task-form-more-options")).toHaveAttribute("hidden"); + + fireEvent.click(advancedToggle); await waitFor(() => { expect(screen.getByText(/Model Configuration/i)).toBeTruthy(); expect(screen.getByText(/Attachments/i)).toBeTruthy(); @@ -287,42 +302,54 @@ describe("NewTaskModal", () => { expect(subtaskButton).not.toBeDisabled(); }); - // FNXC:NewTask 2026-06-22-20:30: The New Task dialog force-opens TaskForm's advanced controls (forceMoreOptionsOpen), so every quick-add control is visible by default with NO disclosure toggle and nothing hidden. - it("shows all advanced fields by default without a More options toggle", () => { + // FNXC:NewTask 2026-06-23-00:10: The New Task dialog NO LONGER force-opens TaskForm's advanced controls. The DEEP/advanced options (model selectors, workflow picker, etc.) are collapsed behind a disclosure relabeled "Advanced"; the common quick-add buttons (Attach/Fast/Priority) are surfaced inline next to Plan and are always visible. + it("keeps deep options behind a collapsed 'Advanced' disclosure while surfacing inline quick-add buttons", () => { renderNewTaskModal(); - const moreOptions = screen.getByTestId("task-form-more-options"); - // No collapse toggle is rendered in the force-open New Task context. - expect(screen.queryByTestId("task-form-more-options-toggle")).toBeNull(); - // The advanced section is open (not hidden) from the start. - expect(moreOptions).not.toHaveAttribute("hidden"); + // The disclosure toggle exists, reads "Advanced", and starts collapsed (section hidden). + const advancedToggle = screen.getByTestId("task-form-more-options-toggle"); + expect(advancedToggle).toHaveTextContent(/Advanced/i); + expect(advancedToggle).toHaveAttribute("aria-expanded", "false"); + // Deep options live inside the collapsed (hidden) section, so they are not shown to the user. + const advancedSection = screen.getByTestId("task-form-more-options"); + expect(advancedSection).toHaveAttribute("hidden"); + expect(advancedSection).toContainElement(screen.getByText(/Model Configuration/i)); + expect(advancedSection).toContainElement(screen.getByText("Workflow")); + + // Inline quick-add buttons (Attach/Fast/Priority) ARE visible without expanding (outside the hidden section). + expect(screen.getByTestId("task-form-inline-attach")).toBeInTheDocument(); + expect(screen.getByTestId("task-form-inline-fast")).toBeInTheDocument(); + expect(screen.getByTestId("task-form-inline-priority")).toBeInTheDocument(); expect(screen.getByTestId("dep-trigger")).toBeInTheDocument(); - // Model Configuration, Attachments, and the Workflow picker are all visible immediately. + // Expanding the disclosure reveals the deep options. + fireEvent.click(advancedToggle); + expect(advancedToggle).toHaveAttribute("aria-expanded", "true"); + expect(screen.getByTestId("task-form-more-options")).not.toHaveAttribute("hidden"); expect(screen.getByText(/Model Configuration/i)).toBeTruthy(); - expect(screen.getByText(/Attachments/i)).toBeTruthy(); expect(screen.getByText("Workflow")).toBeTruthy(); }); it("shows dependencies and agent picker by default", () => { renderNewTaskModal(); - // Both dep-trigger and agent button should be visible by default + // Both dep-trigger and agent button should be visible by default (quick-fields). expect(screen.getByTestId("dep-trigger")).toBeInTheDocument(); expect(screen.getByTestId("new-task-agent-button")).toBeInTheDocument(); - // Advanced options are force-open: no collapse toggle exists. - expect(screen.queryByTestId("task-form-more-options-toggle")).toBeNull(); - expect(screen.getByTestId("task-form-more-options")).not.toHaveAttribute("hidden"); + // The "Advanced" disclosure is collapsed by default. + expect(screen.getByTestId("task-form-more-options-toggle")).toHaveTextContent(/Advanced/i); + expect(screen.getByTestId("task-form-more-options")).toHaveAttribute("hidden"); }); - it("renders dependencies before attachments in form order (quick-fields before More options)", () => { + it("renders dependencies before attachments in form order (quick-fields before Advanced)", () => { renderNewTaskModal(); const dependenciesLabel = screen.getByText("Dependencies"); - // Attachments is in the always-visible advanced section. + // Expand the Advanced disclosure so the Attachments group renders. + fireEvent.click(screen.getByTestId("task-form-more-options-toggle")); const attachmentsLabel = screen.getByText("Attachments"); - // Dependencies (in quick-fields) appears before Attachments (in More options) + // Dependencies (in quick-fields) appears before Attachments (in the Advanced section). expect( dependenciesLabel.compareDocumentPosition(attachmentsLabel) & Node.DOCUMENT_POSITION_FOLLOWING, ).toBe(Node.DOCUMENT_POSITION_FOLLOWING); diff --git a/packages/dashboard/app/components/__tests__/TaskForm.test.tsx b/packages/dashboard/app/components/__tests__/TaskForm.test.tsx index bc2ee2c918..dc75d75b32 100644 --- a/packages/dashboard/app/components/__tests__/TaskForm.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskForm.test.tsx @@ -12,6 +12,9 @@ vi.mock("lucide-react", () => ({ X: () => null, Maximize2: () => null, Minimize2: () => null, + Paperclip: () => null, + Flag: () => null, + Zap: () => null, })); // Mock the api module @@ -140,7 +143,7 @@ describe("TaskForm", () => { onThinkingLevelChange: vi.fn(), }); - fireEvent.click(screen.getByRole("button", { name: /More options/i })); + fireEvent.click(screen.getByTestId("task-form-more-options-toggle")); await waitFor(() => { expect(screen.getByRole("combobox", { name: /Thinking/i })).toBeTruthy();