FN-6985: pack embedded automations panes

Keep the embedded Automations pane list and detail content top-packed instead of spreading vertically.

- Add grid row and alignment rules so narrow embedded Automations content stays content-sized.\n- Cover populated embedded list/detail rendering and CSS contracts for mobile and wide layouts.\n- Add a patch changeset for the published Fusion package.\n\nFiles changed:\n .changeset/fn-6985-automations-spacing.md          |  7 +++\n packages/dashboard/app/components/ScriptsModal.css | 14 +++++-\n .../__tests__/ScheduledTasksModal.test.tsx         | 52 ++++++++++++++++++++++\n 3 files changed, 72 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-6985

Fusion-Task-Lineage: ba2b117a-4dc5-4bc9-9ecb-7bc008cd79cd
This commit is contained in:
gsxdsm
2026-06-25 00:51:50 -07:00
parent f1b3bd8854
commit ef6e4599b1
3 changed files with 72 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix excessive spacing in the embedded Automations pane.
category: fix
dev: Top-pack embedded Automations grid rows and add regression coverage for the list/detail layout.

View File

@@ -1278,15 +1278,27 @@ With the header now edge-to-edge and dividerless, the first body row still needs
padding-left: 0; padding-left: 0;
} }
/* Two-pane body: single column by default (narrow); two columns when the container is wide enough. */ /*
FNXC:Automations 2026-06-24-23:30:
The embedded single-column Automations list/detail grid must not stretch implicit rows to fill the pane; stretch pushes the selected detail card far below the list row on tall/narrow surfaces. Keep rows content-sized and top-aligned so toolbar, list, and detail stay packed while the wide breakpoint only changes the column template.
*/
.automations-two-pane { .automations-two-pane {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-auto-rows: max-content;
align-content: start;
align-items: start;
gap: var(--space-lg); gap: var(--space-lg);
min-height: 0; min-height: 0;
flex: 1; flex: 1;
} }
@media (max-width: 768px) {
.automations-two-pane {
grid-template-columns: 1fr;
}
}
.automations-single-pane { .automations-single-pane {
min-height: 0; min-height: 0;
flex: 1; flex: 1;

View File

@@ -392,6 +392,58 @@ describe("ScheduledTasksModal", () => {
expect(screen.queryByRole("button", { name: "Close" })).toBeNull(); expect(screen.queryByRole("button", { name: "Close" })).toBeNull();
}); });
it("packs populated list and selected detail panes in the embedded list/detail structure", async () => {
mockFetchRoutines.mockResolvedValue([
makeRoutine({ id: "routine-backup", name: "Database Backup", command: "fn backup --create" }),
makeRoutine({ id: "routine-disabled", name: "Disabled Import", command: "fn import", enabled: false }),
]);
const { container } = render(
<ScheduledTasksModal onClose={onClose} addToast={addToast} presentation="embedded" />,
);
await waitFor(() => {
expect(screen.getByRole("option", { name: /database backup/i })).toBeDefined();
});
const twoPane = container.querySelector(".automations-two-pane");
const listPane = container.querySelector(".automations-list-pane");
const detailPane = container.querySelector(".automations-detail-pane");
expect(twoPane).not.toBeNull();
expect(listPane).not.toBeNull();
expect(detailPane).not.toBeNull();
expect(twoPane?.children[0]).toBe(listPane);
expect(twoPane?.children[1]).toBe(detailPane);
expect(screen.getByText("Select an automation")).toBeDefined();
expect(screen.getByText("Disabled")).toBeDefined();
fireEvent.click(screen.getByRole("option", { name: /database backup/i }));
await waitFor(() => {
expect(detailPane?.querySelector(".routine-card .routine-card-name")?.textContent).toBe("Database Backup");
});
expect(screen.getByText("fn backup --create")).toBeDefined();
expect(container.querySelector(".automations-single-pane")).toBeNull();
});
it("keeps embedded automation grid rows top-packed while preserving wide two-pane rules", () => {
const source = readFileSync(resolve(__dirname, "../ScriptsModal.css"), "utf8");
const baseRule = source.match(/\.automations-two-pane\s*\{[^}]*\}/)?.[0] ?? "";
const containerRule = source.match(/@container \(min-width: 900px\)\s*\{\s*\.automations-two-pane\s*\{[^}]*\}/)?.[0] ?? "";
const mediaRule = source.match(/@media \(min-width: 900px\)\s*\{\s*\.automations-two-pane\s*\{[^}]*\}/)?.[0] ?? "";
const mobileRule = source.match(/@media \(max-width: 768px\)\s*\{[\s\S]*?\.automations-two-pane\s*\{[^}]*\}/)?.[0] ?? "";
expect(baseRule).toContain("grid-template-columns: 1fr;");
expect(baseRule).toContain("align-content: start;");
expect(baseRule).toContain("align-items: start;");
expect(baseRule).toContain("grid-auto-rows: max-content;");
expect(containerRule).toContain("grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);");
expect(containerRule).toContain("align-items: start;");
expect(mediaRule).toContain("grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);");
expect(mediaRule).toContain("align-items: start;");
expect(mobileRule).toContain("grid-template-columns: 1fr;");
});
it("does not dismiss on Escape in embedded mode", async () => { it("does not dismiss on Escape in embedded mode", async () => {
render(<ScheduledTasksModal onClose={onClose} addToast={addToast} presentation="embedded" />); render(<ScheduledTasksModal onClose={onClose} addToast={addToast} presentation="embedded" />);