FN-8421: align tablet task modal control heights
Align Task Detail action controls and Planner Chat composer heights across tablet and desktop. - Apply shared compact-height classes to inline Task Detail actions - Use Quick Add control-height tokens and remove conflicting button padding - Size Planner Chat Send and Stop controls to match the composer - Add dashboard regression coverage and a patch changeset Files changed: .../fn-8421-task-detail-tablet-control-heights.md | 7 ++++++ .../dashboard/app/components/TaskDetailModal.css | 18 ++++++++++++--- .../dashboard/app/components/TaskDetailModal.tsx | 11 ++++++--- .../app/components/TaskPlannerChatTab.css | 10 ++++++++ .../__tests__/TaskDetailModal.rendering.test.tsx | 8 +++++++ ...etailModal.responsive-and-dependencies.test.tsx | 13 +++++++++++ .../__tests__/TaskPlannerChatTab.test.tsx | 27 ++++++++++++++++++++++ 7 files changed, 88 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-8421 Fusion-Task-Lineage: 151795c8-4c00-4976-8901-9446557fc1a3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8421-task-detail-tablet-control-heights.md
Normal file
7
.changeset/fn-8421-task-detail-tablet-control-heights.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Task detail Oversight/Fast and chat send heights match sibling controls on tablet.
|
||||||
|
category: fix
|
||||||
|
dev: Completes FN-8287 class wiring on attach/github/priority; aliases detail shared square to Quick Add compact height tokens; equalizes Planner Chat input/send block size for tablet/desktop.
|
||||||
@@ -336,6 +336,13 @@ The task-detail modal metadata must keep priority, execution mode, provenance, P
|
|||||||
and applies at every width, so no mobile-only override is needed here.
|
and applies at every width, so no mobile-only override is needed here.
|
||||||
*/
|
*/
|
||||||
.detail-meta-inline-controls {
|
.detail-meta-inline-controls {
|
||||||
|
/*
|
||||||
|
FNXC:QuickAddActionRow 2026-07-20-12:00:
|
||||||
|
FN-8421 keeps the Task Detail action row at the mobile touch floor while
|
||||||
|
the default compact Quick Add alias serves desktop and tablet widths.
|
||||||
|
*/
|
||||||
|
--detail-priority-control-min-height: var(--quick-entry-action-row-height-mobile);
|
||||||
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +389,14 @@ The task-detail modal metadata must keep priority, execution mode, provenance, P
|
|||||||
low/high/urgent, oversight observe/steer/autonomous/off, execution-mode
|
low/high/urgent, oversight observe/steer/autonomous/off, execution-mode
|
||||||
fast) are untouched.
|
fast) are untouched.
|
||||||
*/
|
*/
|
||||||
--detail-priority-control-min-height: calc(var(--space-lg) + var(--space-lg) + var(--space-xs));
|
/*
|
||||||
|
FNXC:QuickAddActionRow 2026-07-20-12:00:
|
||||||
|
FN-8421 completes FN-8287's shared-square contract. Desktop and tablet must
|
||||||
|
alias Quick Add's compact control token, not make every action equally tall
|
||||||
|
with the former space-calc box; the mobile rule above raises the same alias
|
||||||
|
to the touch-floor token.
|
||||||
|
*/
|
||||||
|
--detail-priority-control-min-height: var(--quick-entry-action-row-height-desktop);
|
||||||
--detail-control-border-radius: var(--radius-md);
|
--detail-control-border-radius: var(--radius-md);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -512,7 +526,6 @@ no raw colors so the tokenized-focus assertion stays green.
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
padding-block: var(--space-xs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-execution-mode-toggle--fast {
|
.detail-execution-mode-toggle--fast {
|
||||||
@@ -698,7 +711,6 @@ affect its size or anchoring — only the trigger's box height changes.
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-oversight-menu-trigger {
|
.detail-oversight-menu-trigger {
|
||||||
padding-block: var(--space-xs);
|
|
||||||
/*
|
/*
|
||||||
FNXC:PlannerOversight 2026-07-05-00:00:
|
FNXC:PlannerOversight 2026-07-05-00:00:
|
||||||
FN-7604 — this trigger is now the single universal surface at every
|
FN-7604 — this trigger is now the single universal surface at every
|
||||||
|
|||||||
@@ -4287,11 +4287,16 @@ export function TaskDetailContent({
|
|||||||
attach, GitHub tracking, Oversight, Priority, then Fast. The compact
|
attach, GitHub tracking, Oversight, Priority, then Fast. The compact
|
||||||
controls delegate to the existing single file-input upload and
|
controls delegate to the existing single file-input upload and
|
||||||
GitHub-tracking handlers so this row never forks persistence paths.
|
GitHub-tracking handlers so this row never forks persistence paths.
|
||||||
|
|
||||||
|
FNXC:QuickAddActionRow 2026-07-20-12:00:
|
||||||
|
FN-8421 completes FN-8287 sizing-class wiring: every mounted inline
|
||||||
|
action carries its shared-square class, preventing Oversight/Fast
|
||||||
|
from using a different tablet-height contract than its siblings.
|
||||||
*/}
|
*/}
|
||||||
<div className="detail-meta-inline-controls" data-testid="detail-meta-inline-controls">
|
<div className="detail-meta-inline-controls" data-testid="detail-meta-inline-controls">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-icon btn-sm"
|
className="btn btn-icon btn-sm detail-inline-attach"
|
||||||
data-testid="detail-inline-attach"
|
data-testid="detail-inline-attach"
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
disabled={uploading}
|
disabled={uploading}
|
||||||
@@ -4303,7 +4308,7 @@ export function TaskDetailContent({
|
|||||||
{canEditGithubTracking && !gitlabTrackedItem && (
|
{canEditGithubTracking && !gitlabTrackedItem && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-icon btn-sm ${githubTrackingEnabled ? "btn-primary" : ""}`}
|
className={`btn btn-icon btn-sm detail-inline-github-toggle ${githubTrackingEnabled ? "btn-primary" : ""}`}
|
||||||
data-testid="detail-inline-github-toggle"
|
data-testid="detail-inline-github-toggle"
|
||||||
onClick={() => void handleToggleGithubTracking()}
|
onClick={() => void handleToggleGithubTracking()}
|
||||||
disabled={isSavingGithubTracking}
|
disabled={isSavingGithubTracking}
|
||||||
@@ -4530,7 +4535,7 @@ export function TaskDetailContent({
|
|||||||
*/}
|
*/}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-icon btn-sm"
|
className="btn btn-icon btn-sm detail-priority-trigger"
|
||||||
data-testid="detail-priority-trigger"
|
data-testid="detail-priority-trigger"
|
||||||
onClick={() => setShowInlinePriorityPicker((isOpen) => !isOpen)}
|
onClick={() => setShowInlinePriorityPicker((isOpen) => !isOpen)}
|
||||||
disabled={isSavingInlinePriority}
|
disabled={isSavingInlinePriority}
|
||||||
|
|||||||
@@ -218,9 +218,19 @@ The Planner Chat streaming Stop button must occupy the same width footprint as t
|
|||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:TaskDetailPlannerChat 2026-07-20-12:00:
|
||||||
|
FN-8421 extends the existing desktop/mobile composer parity to tablet, which
|
||||||
|
inherits these base rules. Send and streaming Stop share this class, so pin
|
||||||
|
its border-box block size and clear button padding to the textarea's compact
|
||||||
|
height rather than relying on the global button minimum.
|
||||||
|
*/
|
||||||
.task-planner-chat-send {
|
.task-planner-chat-send {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
block-size: calc(var(--space-2xl) + var(--space-sm));
|
||||||
min-block-size: calc(var(--space-2xl) + var(--space-sm));
|
min-block-size: calc(var(--space-2xl) + var(--space-sm));
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
/* FN-7634: floor both the Send and Stop button variants (both carry this class) at the same width so the streaming swap never narrows or widens the control. */
|
/* FN-7634: floor both the Send and Stop button variants (both carry this class) at the same width so the streaming swap never narrows or widens the control. */
|
||||||
min-inline-size: var(--chat-input-control-size);
|
min-inline-size: var(--chat-input-control-size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1431,6 +1431,14 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(row.indexOf("detail-inline-github-toggle")).toBeLessThan(row.indexOf("detail-oversight-menu-trigger"));
|
expect(row.indexOf("detail-inline-github-toggle")).toBeLessThan(row.indexOf("detail-oversight-menu-trigger"));
|
||||||
expect(row.indexOf("detail-oversight-menu-trigger")).toBeLessThan(row.indexOf("detail-priority-trigger"));
|
expect(row.indexOf("detail-oversight-menu-trigger")).toBeLessThan(row.indexOf("detail-priority-trigger"));
|
||||||
expect(row.indexOf("detail-priority-trigger")).toBeLessThan(row.indexOf("detail-execution-mode-toggle"));
|
expect(row.indexOf("detail-priority-trigger")).toBeLessThan(row.indexOf("detail-execution-mode-toggle"));
|
||||||
|
// FNXC:QuickAddActionRow 2026-07-20-12:00: Every test-id affordance must
|
||||||
|
// also carry its FN-8287 sizing class, including optional GitHub and
|
||||||
|
// Oversight surfaces, so mounted tablet controls share one compact box.
|
||||||
|
expect(row).toMatch(/className="btn btn-icon btn-sm detail-inline-attach"/);
|
||||||
|
expect(row).toMatch(/className=\{`btn btn-icon btn-sm detail-inline-github-toggle/);
|
||||||
|
expect(row).toMatch(/className="btn btn-icon btn-sm detail-oversight-menu-trigger"/);
|
||||||
|
expect(row).toMatch(/className="btn btn-icon btn-sm detail-priority-trigger"/);
|
||||||
|
expect(row).toMatch(/className=\{`btn btn-icon btn-sm detail-execution-mode-toggle/);
|
||||||
for (const label of ["aria-label", "title"]) {
|
for (const label of ["aria-label", "title"]) {
|
||||||
expect(row.match(new RegExp(label, "g"))?.length).toBeGreaterThanOrEqual(5);
|
expect(row.match(new RegExp(label, "g"))?.length).toBeGreaterThanOrEqual(5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,6 +291,19 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(sharedSizingBlock).toContain("border-width: var(--btn-border-width);");
|
expect(sharedSizingBlock).toContain("border-width: var(--btn-border-width);");
|
||||||
expect(sharedSizingBlock).toContain("border-color: var(--border);");
|
expect(sharedSizingBlock).toContain("border-color: var(--border);");
|
||||||
expect(sharedSizingBlock).toContain("border-radius: var(--detail-control-border-radius);");
|
expect(sharedSizingBlock).toContain("border-radius: var(--detail-control-border-radius);");
|
||||||
|
|
||||||
|
const inlineControlsBlock = getStandaloneCssRuleBlock(css, ".detail-meta-inline-controls");
|
||||||
|
const tabletBlock = getCssAtRuleBlockContaining(css, "@media (min-width: 769px) and (max-width: 1024px)", ".modal.task-detail-modal");
|
||||||
|
const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-meta-inline-controls");
|
||||||
|
const mobileInlineControlsBlock = getCssRuleBlock(mobileBlock, ".detail-meta-inline-controls");
|
||||||
|
|
||||||
|
// FNXC:QuickAddActionRow 2026-07-20-12:00: Equal boxes are insufficient:
|
||||||
|
// desktop and tablet must use Quick Add's compact token, while mobile
|
||||||
|
// deliberately upgrades the same shared alias to its touch-floor token.
|
||||||
|
expect(inlineControlsBlock).toContain("--detail-priority-control-min-height: var(--quick-entry-action-row-height-desktop);");
|
||||||
|
expect(inlineControlsBlock).not.toContain("calc(var(--space-lg) + var(--space-lg) + var(--space-xs))");
|
||||||
|
expect(tabletBlock).not.toMatch(/\.detail-(?:oversight-menu-trigger|execution-mode-toggle)\s*\{[^}]*?(?:height|min-height|width|min-width):/);
|
||||||
|
expect(mobileInlineControlsBlock).toContain("--detail-priority-control-min-height: var(--quick-entry-action-row-height-mobile);");
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip("unifies border/radius/height across the Priority, Execution-mode, and Oversight quick controls (FN-7585)", () => {
|
it.skip("unifies border/radius/height across the Priority, Execution-mode, and Oversight quick controls (FN-7585)", () => {
|
||||||
|
|||||||
@@ -937,6 +937,33 @@ describe("TaskPlannerChatTab", () => {
|
|||||||
expect(mobileTextHideRule).toMatch(/span:not\(\.chat-input-stop-icon\)/);
|
expect(mobileTextHideRule).toMatch(/span:not\(\.chat-input-stop-icon\)/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps Planner Chat input and send/stop height-parity across desktop, tablet, and mobile (FN-8421)", () => {
|
||||||
|
const mobileQueryStart = taskPlannerChatCss.indexOf("@media (max-width: 768px)");
|
||||||
|
const desktopAndTabletCss = mobileQueryStart >= 0 ? taskPlannerChatCss.slice(0, mobileQueryStart) : taskPlannerChatCss;
|
||||||
|
const desktopInputRule = desktopAndTabletCss.match(/\.task-planner-chat-input\s*\{[^}]*\}/)?.[0] ?? "";
|
||||||
|
const desktopSendRule = desktopAndTabletCss.match(/\.task-planner-chat-send\s*\{[^}]*\}/)?.[0] ?? "";
|
||||||
|
const mobileInputRule = taskPlannerChatCss.slice(mobileQueryStart).match(/\.task-planner-chat-input\s*\{[^}]*\}/)?.[0] ?? "";
|
||||||
|
const mobileSendRule = taskPlannerChatCss.slice(mobileQueryStart).match(/\.task-planner-chat-send\s*\{[^}]*\}/)?.[0] ?? "";
|
||||||
|
|
||||||
|
// FNXC:TaskDetailPlannerChat 2026-07-20-12:00: The base contract serves
|
||||||
|
// desktop and the 769–1024 tablet band; explicit border-box sizing and
|
||||||
|
// zero button padding prevent inherited `.btn` padding from re-inflating
|
||||||
|
// the Send or Stop variant above the textarea.
|
||||||
|
const desktopTabletHeight = "calc(var(--space-2xl) + var(--space-sm))";
|
||||||
|
const mobileHeight = "calc(var(--space-2xl) + var(--space-lg))";
|
||||||
|
expect(desktopInputRule).toContain(`height: ${desktopTabletHeight};`);
|
||||||
|
expect(desktopInputRule).toContain(`min-height: ${desktopTabletHeight};`);
|
||||||
|
expect(desktopSendRule).toContain(`block-size: ${desktopTabletHeight};`);
|
||||||
|
expect(desktopSendRule).toContain(`min-block-size: ${desktopTabletHeight};`);
|
||||||
|
expect(desktopSendRule).toContain("box-sizing: border-box;");
|
||||||
|
expect(desktopSendRule).toContain("padding: 0;");
|
||||||
|
expect(mobileInputRule).toContain(`height: ${mobileHeight};`);
|
||||||
|
expect(mobileInputRule).toContain(`min-height: ${mobileHeight};`);
|
||||||
|
expect(mobileSendRule).toContain(`block-size: ${mobileHeight};`);
|
||||||
|
expect(mobileSendRule).toContain(`min-block-size: ${mobileHeight};`);
|
||||||
|
expect(mobileSendRule).toContain("padding: 0;");
|
||||||
|
});
|
||||||
|
|
||||||
it("renders live and stored thinking output through the standard chat surface", async () => {
|
it("renders live and stored thinking output through the standard chat surface", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
mockFetchChatMessages.mockResolvedValueOnce({
|
mockFetchChatMessages.mockResolvedValueOnce({
|
||||||
|
|||||||
Reference in New Issue
Block a user