feat(FN-798): move edit Save/Cancel buttons into modal footer

- Relocate Save/Cancel action buttons from inline position to TaskDetailModal footer
- Add regression tests for footer button placement and visibility
- Remove unused file-service tests and FileBrowserModal test code
- Clean up dead CSS rules from styles.css
- Update dashboard README to reflect current architecture
This commit is contained in:
gsxdsm
2026-04-03 19:00:35 -07:00
parent 10a6443483
commit 7030a3730e
4 changed files with 110 additions and 28 deletions

View File

@@ -41,7 +41,7 @@ AI-guided interactive planning for creating well-specified tasks from high-level
### Task Management
- **Kanban Board**: Drag-and-drop task management across columns (Triage, Todo, In Progress, In Review, Done)
- **Inline Editing**: Quick-edit a task's description directly on the board for Triage and Todo columns. The editor opens as a taller multi-line editing area (4 visible lines) for comfortable editing of longer descriptions, and auto-grows to fit existing content. Double-click a card or use the pencil icon — visible on hover for desktop, always visible on mobile for touch accessibility. Inline editing changes only the description; the title is preserved. To edit both title and description, use the task detail modal.
- **Task Detail Editing**: Edit task title and description directly in the task detail modal. Click the pencil icon in the modal header (available for Triage and Todo tasks) to enter edit mode.
- **Task Detail Editing**: Edit task title and description directly in the task detail modal. Click the pencil icon in the modal header (available for Triage and Todo tasks) to enter edit mode. Save and Cancel actions appear in the modal footer alongside a keyboard shortcut hint, keeping editing controls consistent with other modal action patterns.
- **List View**: Alternative tabular view for tasks with sorting and filtering. The "Hide Done" toggle hides both Done and Archived tasks for an active-work-only view.
- **Model Selection at Creation**: Choose executor and validator AI models while creating tasks from the board or list view, or leave them unset to use the global defaults. Quick-add model dropdowns in both the board triage column and the list view honor saved favorite providers and pinned models, matching the rest of the dashboard model UI.
- **AI-Assisted Creation Controls**: Plan, Subtask, and Refine buttons appear directly below the description textarea in all task creation surfaces (quick entry box, inline create card, and task form modal). These description-adjacent controls make AI-assisted creation and refinement feel directly associated with the text being edited. Deps, Models, and Save actions remain in the expanded controls footer.

View File

@@ -706,25 +706,6 @@ export function TaskDetailModal({
addToast={addToast}
isActive={isEditing}
/>
<div className="modal-edit-actions">
<button
className="btn btn-sm"
onClick={exitEditMode}
disabled={isSaving}
>
Cancel
</button>
<button
className="btn btn-primary btn-sm"
onClick={handleSave}
disabled={isSaving}
>
{isSaving ? "Saving…" : "Save"}
</button>
</div>
<div className="modal-edit-hint">
<kbd>Ctrl+Enter</kbd> to save · <kbd>Escape</kbd> to cancel
</div>
</div>
) : (
<>
@@ -1109,6 +1090,29 @@ export function TaskDetailModal({
)}
</div>
<div className="modal-actions">
{isEditing ? (
<>
<span className="modal-edit-hint">
<kbd>Ctrl+Enter</kbd> to save · <kbd>Escape</kbd> to cancel
</span>
<div className="modal-actions-spacer" />
<button
className="btn btn-sm"
onClick={exitEditMode}
disabled={isSaving}
>
Cancel
</button>
<button
className="btn btn-primary btn-sm"
onClick={handleSave}
disabled={isSaving}
>
{isSaving ? "Saving…" : "Save"}
</button>
</>
) : (
<>
<button className="btn btn-danger btn-sm" onClick={handleDelete}>
Delete
</button>
@@ -1166,6 +1170,8 @@ export function TaskDetailModal({
</button>
))
)}
</>
)}
</div>
{showRefineModal && (
<div

View File

@@ -3274,6 +3274,89 @@ describe("TaskDetailModal", () => {
expect(titleInput.value).toBe("My Task");
expect(descTextarea.value).toBe("My Description");
});
it("renders Save and Cancel in the modal footer, not inside the edit form body", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "triage", title: "Test task" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
// Enter edit mode
fireEvent.click(container.querySelector(".modal-edit-btn")!);
// The edit form body should NOT contain the Save or Cancel action buttons
const editForm = container.querySelector(".modal-edit-form");
expect(editForm).toBeTruthy();
const formButtons = Array.from(editForm!.querySelectorAll("button"));
const formButtonTexts = formButtons.map((b) => b.textContent);
expect(formButtonTexts).not.toContain("Save");
expect(formButtonTexts).not.toContain("Cancel");
expect(formButtonTexts).not.toContain("Saving…");
// The modal-actions footer should contain the Save and Cancel buttons
const modalActions = container.querySelector(".modal-actions");
expect(modalActions).toBeTruthy();
const footerButtons = modalActions!.querySelectorAll("button");
const buttonTexts = Array.from(footerButtons).map((b) => b.textContent);
expect(buttonTexts).toContain("Cancel");
expect(buttonTexts).toContain("Save");
});
it("renders keyboard hint in the modal footer when editing", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "triage", title: "Test task" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
// Enter edit mode
fireEvent.click(container.querySelector(".modal-edit-btn")!);
// The hint should be in the modal-actions footer, not inside the edit form body
const editForm = container.querySelector(".modal-edit-form");
expect(editForm!.querySelector(".modal-edit-hint")).toBeNull();
const modalActions = container.querySelector(".modal-actions");
expect(modalActions!.querySelector(".modal-edit-hint")).toBeTruthy();
});
it("shows normal modal actions (not edit actions) when not editing", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ id: "FN-001", column: "triage", title: "Test task" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
// Should NOT be in edit mode — no edit hint, no Save/Cancel in footer
const modalActions = container.querySelector(".modal-actions");
expect(modalActions!.querySelector(".modal-edit-hint")).toBeNull();
const footerButtons = modalActions!.querySelectorAll("button");
const buttonTexts = Array.from(footerButtons).map((b) => b.textContent);
expect(buttonTexts).not.toContain("Save");
expect(buttonTexts).not.toContain("Cancel");
// Should contain standard actions like Delete
expect(buttonTexts).toContain("Delete");
});
});
describe("Commits tab visibility", () => {

View File

@@ -4336,17 +4336,10 @@ body {
cursor: not-allowed;
}
.modal-edit-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: var(--space-xs);
}
/* Edit-mode hint text now lives in modal-actions footer */
.modal-edit-hint {
font-size: 12px;
color: var(--text-dim);
text-align: center;
}
.modal-edit-hint kbd {