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) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-22 09:58:18 -07:00
parent 4626f8e7fe
commit 8e1e0920df
5 changed files with 137 additions and 27 deletions

View File

@@ -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 {

View File

@@ -850,7 +850,6 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
renderBelowPrimary={quickFields}
hideDependencies={true}
autoExpandMoreOptionsOnSelection={false}
forceMoreOptionsOpen={true}
/>
</div>

View File

@@ -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({
</div>
</div>
{/* 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" && (
<div className="task-form-description-actions" data-testid="task-form-description-actions">
{onPlanningMode && (
<button
@@ -878,13 +888,70 @@ export function TaskForm({
{t("taskForm.subtaskButton", "Subtask")}
</button>
)}
{/* FNXC:NewTask 2026-06-23-00:10: Attach — reuses the Advanced section's hidden file input; programmatic .click() works even while that section is collapsed. */}
<button
type="button"
className="btn btn-sm"
onClick={() => fileInputRef.current?.click()}
disabled={disabled}
data-testid="task-form-inline-attach"
title={t("taskForm.attachScreenshot", "Attach Screenshot")}
>
<Paperclip size={12} style={{ verticalAlign: "middle", marginRight: 4 }} />
{pendingImages.length > 0
? t("taskForm.attachCount", "Attach ({{count}})", { count: pendingImages.length })
: t("taskForm.attach", "Attach")}
</button>
{/* FNXC:NewTask 2026-06-23-00:10: Fast — toggles executionMode standard⇄fast; btn-primary when active, matching QuickEntryBox's fast toggle. */}
{onExecutionModeChange && executionMode !== undefined && (
<button
type="button"
className={`btn btn-sm ${executionMode === "fast" ? "btn-primary" : ""}`}
onClick={() => onExecutionModeChange(executionMode === "fast" ? "standard" : "fast")}
aria-pressed={executionMode === "fast"}
disabled={disabled}
data-testid="task-form-inline-fast"
title={t("taskForm.toggleFastMode", "Toggle fast execution mode")}
>
<Zap size={12} style={{ verticalAlign: "middle", marginRight: 4 }} />
{t("taskForm.fast", "Fast")}
</button>
)}
{/* FNXC:NewTask 2026-06-23-00:10: Priority — cycles TASK_PRIORITIES via onPriorityChange (Flag affordance, same label shape as QuickEntryBox). */}
{onPriorityChange && (
<button
type="button"
className="btn btn-sm"
onClick={() => {
const current = priority ?? DEFAULT_TASK_PRIORITY;
const idx = TASK_PRIORITIES.indexOf(current);
const next = TASK_PRIORITIES[(idx + 1) % TASK_PRIORITIES.length];
onPriorityChange(next);
}}
disabled={disabled}
data-testid="task-form-inline-priority"
title={t("taskForm.priorityLabel", "Priority")}
>
<Flag size={12} style={{ verticalAlign: "middle", marginRight: 4 }} />
{(() => {
const p = priority ?? DEFAULT_TASK_PRIORITY;
return `${p[0].toUpperCase()}${p.slice(1)}`;
})()}
</button>
)}
</div>
)}
</div>
{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 && (
<button
type="button"
@@ -895,7 +962,7 @@ export function TaskForm({
disabled={disabled}
data-testid="task-form-more-options-toggle"
>
<span>{t("taskForm.moreOptions", "More options")}</span>
<span>{t("taskForm.advancedOptions", "Advanced")}</span>
{showMoreOptions ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
</button>
)}

View File

@@ -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);

View File

@@ -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();