FN-7349: expand Plan prompt editor width
Make Plan prompt editing surfaces span the task-detail layout consistently. - Scope a Plan prompt wrapper around markdown, empty, inline edit, and revision controls. - Let SpecEditor action rows and text inputs shrink and wrap within modal, embedded, and mobile layouts. - Cover full-width Plan prompt behavior with task-detail CSS and rendering tests. Files changed: .changeset/plan-prompt-full-width.md | 7 ++ packages/dashboard/app/components/SpecEditor.css | 27 ++++- .../dashboard/app/components/TaskDetailModal.css | 58 ++++++++- .../dashboard/app/components/TaskDetailModal.tsx | 13 +- .../TaskDetailModal.definition-actions.test.tsx | 134 ++++++++++++--------- ...etailModal.responsive-and-dependencies.test.tsx | 44 +++++++ 6 files changed, 219 insertions(+), 64 deletions(-) Fusion-Task-Id: FN-7349 Fusion-Task-Lineage: 65defcea-ce70-49fb-bd00-2127239e09d0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/plan-prompt-full-width.md
Normal file
7
.changeset/plan-prompt-full-width.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Make the task Plan prompt editor span the full task-detail card width.
|
||||
category: fix
|
||||
dev: Adds scoped TaskDetailModal Plan prompt width guards for modal, embedded, and mobile surfaces.
|
||||
@@ -39,15 +39,25 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
/* Edit mode container in Definition tab */
|
||||
/*
|
||||
FNXC:TaskDetailPlan 2026-06-30-00:00:
|
||||
Inline Plan editing and AI revision controls share SpecEditor primitives, so these rows must shrink within task-detail modal and embedded cards instead of imposing a narrow or overflowing editor column.
|
||||
*/
|
||||
.spec-editor-edit-mode {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Content area - flexible and scrollable */
|
||||
@@ -61,7 +71,10 @@
|
||||
|
||||
/* Textarea - fills available space in edit mode */
|
||||
.spec-editor-textarea {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
min-height: 200px;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
@@ -121,6 +134,10 @@
|
||||
|
||||
/* AI Revision section - fixed at bottom */
|
||||
.spec-editor-revision {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
@@ -140,7 +157,10 @@
|
||||
}
|
||||
|
||||
.spec-editor-feedback {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding: 10px 12px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
@@ -168,6 +188,11 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
|
||||
@@ -2007,6 +2007,42 @@ The desktop table needs a token-scale min-width and non-anywhere model-name wrap
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:TaskDetailPlan 2026-06-30-00:00:
|
||||
The Plan prompt editor is a task-detail body surface, not a narrow prose inset. Scope full-width guards to the Plan prompt section so modal and embedded cards align markdown, empty fallback, inline edit, and AI revision controls without changing unrelated detail sections.
|
||||
*/
|
||||
.detail-section--plan-prompt {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.detail-section--plan-prompt .markdown-body,
|
||||
.detail-section--plan-prompt .detail-prompt,
|
||||
.detail-section--plan-prompt .spec-loading,
|
||||
.detail-section--plan-prompt .spec-editor-edit-mode,
|
||||
.detail-section--plan-prompt .spec-editor-revision,
|
||||
.detail-section--plan-prompt .spec-editor-textarea,
|
||||
.detail-section--plan-prompt .spec-editor-feedback {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.detail-section--plan-prompt .markdown-body,
|
||||
.detail-section--plan-prompt .detail-prompt {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.task-detail-content--embedded .detail-section--plan-prompt {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Back button for changed-files modal - hidden on desktop */
|
||||
.changed-files-back-button {
|
||||
display: none;
|
||||
@@ -2196,6 +2232,26 @@ The desktop table needs a token-scale min-width and non-anywhere model-name wrap
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.detail-section--plan-prompt .detail-spec-edit-trigger,
|
||||
.detail-section--plan-prompt .spec-editor-actions-row,
|
||||
.detail-section--plan-prompt .spec-editor-revision-actions {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.detail-section--plan-prompt .spec-editor-actions-row,
|
||||
.detail-section--plan-prompt .spec-editor-revision-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.detail-section--plan-prompt .spec-editor-actions-row .btn,
|
||||
.detail-section--plan-prompt .spec-editor-revision-actions .btn {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.detail-section--plan-prompt .spec-editor-char-count {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-section h4 {
|
||||
@@ -2222,7 +2278,7 @@ The desktop table needs a token-scale min-width and non-anywhere model-name wrap
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.spec-loading::before {
|
||||
|
||||
@@ -3923,13 +3923,16 @@ export function TaskDetailContent({
|
||||
<div className="step-progress-empty">{t("taskDetail.progress.noSteps", "(no steps defined)")}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="detail-section">
|
||||
<div className="detail-section detail-section--plan-prompt">
|
||||
{!isEditingSpec && (
|
||||
<div className="detail-spec-edit-trigger">
|
||||
{/*
|
||||
FNXC:TaskDetailPlan 2026-06-30-00:00:
|
||||
The Plan tab keeps the internal definition route for stable links, while exposing a direct PROMPT.md editor action so operators can comment on the executable task plan file without replacing the inline AI revision flow.
|
||||
*/}
|
||||
{/**
|
||||
* FNXC:TaskDetailPlan 2026-06-30-00:00:
|
||||
* The Plan tab keeps the internal definition route for stable links, while exposing a direct PROMPT.md editor action so operators can comment on the executable task plan file without replacing the inline AI revision flow.
|
||||
*
|
||||
* FNXC:TaskDetailPlan 2026-06-30-00:00:
|
||||
* The Plan prompt surfaces must span the task-detail card body in modal and embedded renderings. Keep the scoped wrapper around markdown, no-prompt fallback, inline edit, and AI revision controls so width fixes do not alter unrelated detail sections.
|
||||
*/}
|
||||
{fileBrowser && (
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
|
||||
@@ -83,18 +83,78 @@ describe("TaskDetailModal", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const planSection = container.querySelector(".detail-section--plan-prompt");
|
||||
expect(planSection).toBeTruthy();
|
||||
// Initially showing markdown view
|
||||
expect(container.querySelector(".markdown-body")).toBeTruthy();
|
||||
const markdown = container.querySelector(".markdown-body");
|
||||
expect(markdown).toBeTruthy();
|
||||
expect(planSection?.contains(markdown)).toBe(true);
|
||||
|
||||
// Click Edit button
|
||||
fireEvent.click(screen.getByText("Edit"));
|
||||
|
||||
// Should show spec edit textarea (query by class for specificity)
|
||||
const editMode = container.querySelector(".spec-editor-edit-mode");
|
||||
const textarea = container.querySelector(".spec-editor-textarea") as HTMLTextAreaElement;
|
||||
const feedback = container.querySelector(".spec-editor-feedback");
|
||||
expect(editMode).toBeTruthy();
|
||||
expect(textarea).toBeTruthy();
|
||||
expect(feedback).toBeTruthy();
|
||||
expect(planSection?.contains(editMode)).toBe(true);
|
||||
expect(planSection?.contains(textarea)).toBe(true);
|
||||
expect(planSection?.contains(feedback)).toBe(true);
|
||||
expect(textarea.value).toBe("# Test\n\nSpec content.");
|
||||
});
|
||||
|
||||
it("keeps the no-prompt fallback inside the scoped full-width Plan wrapper", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ prompt: "" })}
|
||||
initialTab="definition"
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
const planSection = container.querySelector(".detail-section--plan-prompt");
|
||||
const fallback = container.querySelector(".detail-prompt");
|
||||
expect(planSection).toBeTruthy();
|
||||
expect(fallback).toBeTruthy();
|
||||
expect(planSection?.contains(fallback)).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps embedded Plan edit controls inside the full-width wrapper", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailContent
|
||||
task={makeTask({ prompt: "# Embedded\n\nSpec content." })}
|
||||
initialTab="definition"
|
||||
embedded
|
||||
onRequestClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.querySelector(".task-detail-content--embedded")).toBeTruthy();
|
||||
const planSection = container.querySelector(".detail-section--plan-prompt");
|
||||
fireEvent.click(screen.getByText("Edit"));
|
||||
|
||||
const editMode = container.querySelector(".spec-editor-edit-mode");
|
||||
const textarea = container.querySelector(".spec-editor-textarea");
|
||||
const feedback = container.querySelector(".spec-editor-feedback");
|
||||
expect(planSection).toBeTruthy();
|
||||
expect(planSection?.contains(editMode)).toBe(true);
|
||||
expect(planSection?.contains(textarea)).toBe(true);
|
||||
expect(planSection?.contains(feedback)).toBe(true);
|
||||
});
|
||||
|
||||
it("clicking Cancel returns to view mode without saving", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
@@ -222,20 +282,11 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
// In-progress tasks show exactly 11 tabs:
|
||||
// Activity, Plan, Logs, Changes, Review, Comments, Artifacts, Model, Workflow, Stats, Routing
|
||||
// Activity, Chat, Plan, Changes, Review, Comments, Artifacts, Model, Workflow, Stats, Routing
|
||||
const tabs = container.querySelectorAll(".detail-tab");
|
||||
expect(tabs.length).toBe(11);
|
||||
expect(tabs[0].textContent).toBe("Activity");
|
||||
expect(tabs[1].textContent).toBe("Plan");
|
||||
expect(tabs[2].textContent).toBe("Logs");
|
||||
expect(tabs[3].textContent).toBe("Changes");
|
||||
expect(tabs[4].textContent).toBe("Review");
|
||||
expect(tabs[5].textContent).toBe("Comments");
|
||||
expect(tabs[6].textContent).toBe("Artifacts");
|
||||
expect(tabs[7].textContent).toBe("Model");
|
||||
expect(tabs[8].textContent).toBe("Workflow");
|
||||
expect(tabs[9].textContent).toBe("Stats");
|
||||
expect(tabs[10].textContent).toBe("Routing");
|
||||
expect(Array.from(tabs).map(t => t.textContent)).toEqual([
|
||||
"Activity", "Chat", "Plan", "Changes", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing",
|
||||
]);
|
||||
// Commits tab should NOT be present for non-done tasks
|
||||
expect(screen.queryByText("Commits")).toBeNull();
|
||||
});
|
||||
@@ -256,18 +307,9 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// In-progress task with workflow steps: 11 tabs (Review after Changes, Workflow after Model)
|
||||
const tabs = container.querySelectorAll(".detail-tab");
|
||||
expect(tabs.length).toBe(11);
|
||||
expect(tabs[0].textContent).toBe("Activity");
|
||||
expect(tabs[1].textContent).toBe("Plan");
|
||||
expect(tabs[2].textContent).toBe("Logs");
|
||||
expect(tabs[3].textContent).toBe("Changes");
|
||||
expect(tabs[4].textContent).toBe("Review");
|
||||
expect(tabs[5].textContent).toBe("Comments");
|
||||
expect(tabs[6].textContent).toBe("Artifacts");
|
||||
expect(tabs[7].textContent).toBe("Model");
|
||||
expect(tabs[8].textContent).toBe("Workflow");
|
||||
expect(tabs[9].textContent).toBe("Stats");
|
||||
expect(tabs[10].textContent).toBe("Routing");
|
||||
expect(Array.from(tabs).map(t => t.textContent)).toEqual([
|
||||
"Activity", "Chat", "Plan", "Changes", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does NOT show Commits tab for done task with mergeDetails.commitSha (changes merged into Changes tab)", () => {
|
||||
@@ -287,21 +329,11 @@ describe("TaskDetailModal", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
// Done task with commit SHA: Activity, Summary, Plan, Logs, Changes, Review, Comments, Artifacts, Model, Workflow, Stats, Routing (12 tabs, no Commits)
|
||||
// Done task with commit SHA: Activity, Chat, Summary, Plan, Changes, Review, Comments, Artifacts, Model, Workflow, Stats, Routing (12 tabs, no Commits)
|
||||
const tabs = container.querySelectorAll(".detail-tab");
|
||||
expect(tabs.length).toBe(12);
|
||||
expect(tabs[0].textContent).toBe("Activity");
|
||||
expect(tabs[1].textContent).toBe("Summary");
|
||||
expect(tabs[2].textContent).toBe("Plan");
|
||||
expect(tabs[3].textContent).toBe("Logs");
|
||||
expect(tabs[4].textContent).toBe("Changes");
|
||||
expect(tabs[5].textContent).toBe("Review");
|
||||
expect(tabs[6].textContent).toBe("Comments");
|
||||
expect(tabs[7].textContent).toBe("Artifacts");
|
||||
expect(tabs[8].textContent).toBe("Model");
|
||||
expect(tabs[9].textContent).toBe("Workflow");
|
||||
expect(tabs[10].textContent).toBe("Stats");
|
||||
expect(tabs[11].textContent).toBe("Routing");
|
||||
expect(Array.from(tabs).map(t => t.textContent)).toEqual([
|
||||
"Activity", "Chat", "Summary", "Plan", "Changes", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing",
|
||||
]);
|
||||
// Commits tab should NOT be present
|
||||
expect(screen.queryByText("Commits")).toBeNull();
|
||||
});
|
||||
@@ -326,19 +358,9 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
// Done task with workflow steps and commit SHA: 12 tabs including Summary and Review (no Commits)
|
||||
const tabs = container.querySelectorAll(".detail-tab");
|
||||
expect(tabs.length).toBe(12);
|
||||
expect(tabs[0].textContent).toBe("Activity");
|
||||
expect(tabs[1].textContent).toBe("Summary");
|
||||
expect(tabs[2].textContent).toBe("Plan");
|
||||
expect(tabs[3].textContent).toBe("Logs");
|
||||
expect(tabs[4].textContent).toBe("Changes");
|
||||
expect(tabs[5].textContent).toBe("Review");
|
||||
expect(tabs[6].textContent).toBe("Comments");
|
||||
expect(tabs[7].textContent).toBe("Artifacts");
|
||||
expect(tabs[8].textContent).toBe("Model");
|
||||
expect(tabs[9].textContent).toBe("Workflow");
|
||||
expect(tabs[10].textContent).toBe("Stats");
|
||||
expect(tabs[11].textContent).toBe("Routing");
|
||||
expect(Array.from(tabs).map(t => t.textContent)).toEqual([
|
||||
"Activity", "Chat", "Summary", "Plan", "Changes", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing",
|
||||
]);
|
||||
// Commits tab should NOT be present
|
||||
expect(screen.queryByText("Commits")).toBeNull();
|
||||
});
|
||||
@@ -358,9 +380,8 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
const triageTabs = triageContainer.querySelectorAll(".detail-tab");
|
||||
expect(triageTabs.length).toBe(10); // Activity, Plan, Logs, Review, Comments, Artifacts, Model, Workflow, Stats, Routing
|
||||
expect(Array.from(triageTabs).map(t => t.textContent)).toEqual([
|
||||
"Activity", "Plan", "Logs", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing",
|
||||
"Activity", "Chat", "Plan", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing",
|
||||
]);
|
||||
|
||||
const { container: todoContainer } = render(
|
||||
@@ -377,9 +398,8 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
const todoTabs = todoContainer.querySelectorAll(".detail-tab");
|
||||
expect(todoTabs.length).toBe(10); // Activity, Plan, Logs, Review, Comments, Artifacts, Model, Workflow, Stats, Routing
|
||||
expect(Array.from(todoTabs).map(t => t.textContent)).toEqual([
|
||||
"Activity", "Plan", "Logs", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing",
|
||||
"Activity", "Chat", "Plan", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing",
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -283,6 +283,50 @@ describe("TaskDetailModal", () => {
|
||||
expect(tabletModalBlock).not.toContain("16px");
|
||||
});
|
||||
|
||||
it("keeps Plan prompt surfaces full-width across modal, embedded, and mobile task-detail layouts", () => {
|
||||
const css = readDashboardStylesSource();
|
||||
const planBlock = getExactCssRuleBlock(css, ".detail-section--plan-prompt");
|
||||
const planSurfaceBlock = getExactCssRuleBlock(css, ".detail-section--plan-prompt .markdown-body,\n.detail-section--plan-prompt .detail-prompt,\n.detail-section--plan-prompt .spec-loading,\n.detail-section--plan-prompt .spec-editor-edit-mode,\n.detail-section--plan-prompt .spec-editor-revision,\n.detail-section--plan-prompt .spec-editor-textarea,\n.detail-section--plan-prompt .spec-editor-feedback");
|
||||
const embeddedPlanBlock = getExactCssRuleBlock(css, ".task-detail-content--embedded .detail-section--plan-prompt");
|
||||
const editModeBlock = getExactCssRuleBlock(css, ".spec-editor-edit-mode");
|
||||
const textareaBlock = getExactCssRuleBlock(css, ".spec-editor-textarea");
|
||||
const feedbackBlock = getExactCssRuleBlock(css, ".spec-editor-feedback");
|
||||
const actionsBlock = getExactCssRuleBlock(css, ".spec-editor-actions-row");
|
||||
const revisionActionsBlock = getExactCssRuleBlock(css, ".spec-editor-revision-actions");
|
||||
const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-section--plan-prompt .spec-editor-actions-row");
|
||||
|
||||
for (const [surface, block] of [
|
||||
["Plan wrapper", planBlock],
|
||||
["Plan prompt descendants", planSurfaceBlock],
|
||||
["embedded Plan wrapper", embeddedPlanBlock],
|
||||
["edit mode", editModeBlock],
|
||||
["textarea", textareaBlock],
|
||||
["feedback", feedbackBlock],
|
||||
["save/cancel actions", actionsBlock],
|
||||
["AI revision actions", revisionActionsBlock],
|
||||
] as const) {
|
||||
expect(block, `${surface} width`).toContain("width: 100%;");
|
||||
expect(block, `${surface} min-width`).toContain("min-width: 0;");
|
||||
expect(block, `${surface} max-width`).toContain("max-width: 100%;");
|
||||
}
|
||||
|
||||
expect(planBlock).toContain("display: flex;");
|
||||
expect(planBlock).toContain("flex-direction: column;");
|
||||
expect(planSurfaceBlock).toContain("box-sizing: border-box;");
|
||||
expect(textareaBlock).toContain("box-sizing: border-box;");
|
||||
expect(feedbackBlock).toContain("box-sizing: border-box;");
|
||||
expect(actionsBlock).toContain("flex-wrap: wrap;");
|
||||
expect(revisionActionsBlock).toContain("flex-wrap: wrap;");
|
||||
expect(mobileBlock).toContain(".detail-section--plan-prompt .spec-editor-actions-row,");
|
||||
expect(mobileBlock).toContain(".detail-section--plan-prompt .spec-editor-revision-actions");
|
||||
expect(mobileBlock).toContain("align-items: stretch;");
|
||||
expect(mobileBlock).toContain("flex-wrap: wrap;");
|
||||
expect(mobileBlock).toContain(".detail-section--plan-prompt .spec-editor-actions-row .btn,");
|
||||
expect(mobileBlock).toContain(".detail-section--plan-prompt .spec-editor-revision-actions .btn");
|
||||
expect(mobileBlock).toContain("flex: 1 1 auto;");
|
||||
expect(css).not.toMatch(/\.detail-section\s*\{[^}]*width:\s*100%;/);
|
||||
});
|
||||
|
||||
it("keeps task-detail tabs as horizontal scrollers across modal, embedded, mobile, and tablet surfaces", () => {
|
||||
const css = readDashboardStylesSource();
|
||||
const baseTabsBlock = getExactCssRuleBlock(css, ".detail-tabs");
|
||||
|
||||
Reference in New Issue
Block a user