diff --git a/packages/dashboard/app/components/TaskCard.css b/packages/dashboard/app/components/TaskCard.css index a76f927f2e..bbd80d869d 100644 --- a/packages/dashboard/app/components/TaskCard.css +++ b/packages/dashboard/app/components/TaskCard.css @@ -134,12 +134,18 @@ FN-7862 requires the task id, the first badge row, and the right-side controls t /* FNXC:TaskCardLayout 2026-07-12-00:00: FN-7871 requires the mono task id to read optically centered with the first-row header badges and right-side icons/chips. Keep the outer FN-7862 flex-start first-row anchor and FN-7837 middle-badge wrapping intact; use a token-derived visual nudge on the id itself because its line-height: 1 glyph box sits slightly high inside the shared chip-height row. + +FNXC:TaskCardLayout 2026-07-13-01:01: +Lock the id to the same chip-height row as .card-header-actions so the whole right cluster (Actions/⋯/size) shares one optical baseline with FN-#### on desktop and mobile. */ .card-id { display: inline-flex; align-items: center; flex-shrink: 0; + box-sizing: border-box; + height: var(--card-chip-height); min-height: var(--card-chip-height); + max-height: var(--card-chip-height); line-height: 1; transform: translateY(calc(var(--space-xs) / 4)); font-size: 0.6875rem; @@ -1436,6 +1442,9 @@ executing. These map 1:1 to the unified progress status so the dot color encodes /* FNXC:TaskCardLayout 2026-07-12-00:00: FN-7889 requires the right-side size/menu icons and action chips to share the mono task id's optical centerline. Mirror FN-7871's token-derived id nudge on the whole right cluster while preserving FN-7862's flex-start first-row anchor and FN-7837's middle-badge wrap/size-chip-not-orphaned contract. + +FNXC:TaskCardLayout 2026-07-13-01:00: +The whole right cluster (Actions/Send-back, ⋯ menu, size badge) must share the task-id first-row baseline. Lock the cluster to the chip height so a larger touch-target child (28px ⋯) cannot grow the flex line and sink Actions/size below the id; overflow stays visible so the hit box can extend outside the locked row. */ .card-header-actions { display: flex; @@ -1443,8 +1452,12 @@ FN-7889 requires the right-side size/menu icons and action chips to share the mo align-self: flex-start; flex: 0 0 auto; gap: var(--space-xs); + box-sizing: border-box; + height: var(--card-chip-height); min-height: var(--card-chip-height); + max-height: var(--card-chip-height); margin-left: auto; + overflow: visible; transform: translateY(calc(var(--space-xs) / 4)); } @@ -1548,13 +1561,20 @@ short-landscape variant (FN-5751 lesson: never a desktop-only or portrait-only f outline-offset: 1px; } -/* FNXC:TaskCardMenu 2026-07-10-12:00: mobile has no hover — the ⋯ affordance must be always visible - with a comfortable touch target. Includes short-landscape phones per the project mobile MQ. */ +/* +FNXC:TaskCardMenu 2026-07-10-12:00: +Mobile has no hover — the ⋯ affordance must be always visible with a comfortable touch target. Includes short-landscape phones per the project mobile MQ. + +FNXC:TaskCardLayout 2026-07-13-00:59: +The mobile/short-landscape 28px ⋯ touch target must not expand .card-header-actions' flex line. Without a negative vertical margin (same cancel pattern as .card-edit-btn/.card-delete-btn), align-items:center sinks the Actions/Send-back trigger and size badge below the mono task-id baseline on mobile. Keep the 28px hit box; cancel only the layout contribution so the right cluster stays vertically aligned with the task id (follow-up to FN-7933's within-cluster centerline). +*/ @media (max-width: 768px), (max-height: 480px) { .card-menu-btn { opacity: 1; width: 28px; height: 28px; + /* Match edit/delete: cancel 28px layout growth while preserving the larger tap target. */ + margin: -6px 0; line-height: 1; border-radius: var(--radius-md); } @@ -1826,11 +1846,25 @@ FN-7928 requires the Send-back/Actions trigger, ⋯ menu button, and size badge /* FNXC:TaskCardLayout 2026-07-13-00:00: FN-7933 follows FN-7928 by keeping the Send-back/Actions trigger, ⋯ menu button, and size badge on one optical vertical center inside the header actions at the mobile breakpoint. Preserve FN-7889's cluster↔id nudge, FN-7862's flex-start header anchor, FN-7837's badge-wrap/size-chip grouping, FN-7928's desktop centering, and FN-4351's no-min-height mobile rule for the send-back/menu buttons. + + FNXC:TaskCardLayout 2026-07-13-01:01: + On mobile the entire right row (Actions/Send-back + ⋯ + size) must sit on the same first-row baseline as the task id. Lock the cluster (and the mono id) to the mobile chip height so the 28px ⋯ touch target cannot stretch the row and drop the whole right cluster below FN-#### / DONE. overflow:visible keeps the larger hit boxes paintable outside the locked row. */ + .card-id { + height: var(--card-chip-height-mobile); + min-height: var(--card-chip-height-mobile); + max-height: var(--card-chip-height-mobile); + box-sizing: border-box; + } + .card-header-actions { align-items: center; gap: calc(var(--space-xs) / 2); flex-shrink: 0; + height: var(--card-chip-height-mobile); + min-height: var(--card-chip-height-mobile); + max-height: var(--card-chip-height-mobile); + overflow: visible; } .card-menu-btn, @@ -1838,6 +1872,13 @@ FN-7928 requires the Send-back/Actions trigger, ⋯ menu button, and size badge line-height: 1; } + /* Keep Actions/Send-back on the locked row centerline (no extra vertical box growth). */ + .card-send-back { + display: inline-flex; + align-items: center; + height: 100%; + } + /* FN-4351/FN-3965: keep secondary actions visible on touch, but compact per WCAG 2.5.8 because the card tap surface is the primary target for opening task detail. */ .card-archive-btn, .card-unarchive-btn, @@ -1848,6 +1889,8 @@ FN-7928 requires the Send-back/Actions trigger, ⋯ menu button, and size badge .card-send-back-btn { opacity: 1; line-height: 1; + margin-top: 0; + margin-bottom: 0; } .card-promote-action.card-send-back-btn { diff --git a/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx index 7e3c23cc24..9df845a336 100644 --- a/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx @@ -73,11 +73,17 @@ function expectSharedHeaderBaseline(container: HTMLElement) { expect(idStyles.alignItems).toBe("center"); expect(idStyles.lineHeight).toBe("1"); expect(idStyles.minHeight).toMatch(resolvedChipHeightPattern); + expect(idStyles.height).toMatch(resolvedChipHeightPattern); + expect(idStyles.maxHeight).toMatch(resolvedChipHeightPattern); expect(idStyles.transform).toMatch(centeredIdNudgePattern); expect(actionsStyles.display).toBe("flex"); expect(actionsStyles.alignItems).toBe("center"); expect(actionsStyles.transform).toBe(idStyles.transform); expect(actionsStyles.minHeight).toMatch(resolvedChipHeightPattern); + // Locked chip-height row so a taller ⋯ touch target cannot sink the right cluster below the task id. + expect(actionsStyles.height).toMatch(resolvedChipHeightPattern); + expect(actionsStyles.maxHeight).toMatch(resolvedChipHeightPattern); + expect(actionsStyles.overflow).toBe("visible"); expect(actionsStyles.marginLeft).toBe("auto"); expect(actionsStyles.flex).toBe("0 0 auto"); } @@ -479,10 +485,15 @@ describe("TaskCard badge wrapping (FN-5162)", () => { const actionsRule = loadedCss.match(/\.card-header-actions\s*\{(?[^}]*)\}/)?.groups?.body ?? ""; expect(cardHeaderRule).toContain("align-items: flex-start;"); expect(cardIdRule).toContain("min-height: var(--card-chip-height);"); + expect(cardIdRule).toContain("height: var(--card-chip-height);"); + expect(cardIdRule).toContain("max-height: var(--card-chip-height);"); expect(cardIdRule).toContain("line-height: 1;"); expect(cardIdRule).toContain("transform: translateY(calc(var(--space-xs) / 4));"); expect(cardIdRule).not.toMatch(/translateY\(\d/); expect(actionsRule).toContain("align-items: center;"); + expect(actionsRule).toContain("height: var(--card-chip-height);"); + expect(actionsRule).toContain("max-height: var(--card-chip-height);"); + expect(actionsRule).toContain("overflow: visible;"); expect(actionsRule).toContain("transform: translateY(calc(var(--space-xs) / 4));"); expect(actionsRule).not.toMatch(/translateY\(\d/); expect(loadedCss).toContain(".card-id,\n .card-header-badges,\n .card-header-actions"); @@ -494,8 +505,16 @@ describe("TaskCard badge wrapping (FN-5162)", () => { const menuTouchSection = getCssBlocks(loadedCss, "max-height: 480px").join("\n"); expectCssRuleToContain(mobileSection, ".card-header-actions", "min-height: var(--card-chip-height-mobile);"); + expectCssRuleToContain(mobileSection, ".card-header-actions", "height: var(--card-chip-height-mobile);"); + expectCssRuleToContain(mobileSection, ".card-header-actions", "max-height: var(--card-chip-height-mobile);"); + expectCssRuleToContain(mobileSection, ".card-header-actions", "overflow: visible;"); expectCssRuleToContain(mobileSection, ".card-header-actions", "align-items: center;"); expectCssRuleToContain(mobileSection, ".card-header-actions", "gap: calc(var(--space-xs) / 2);"); + // Task id and right cluster share the same locked mobile chip row so Actions/⋯/size sit on the FN-#### baseline. + expectCssRuleToContain(mobileSection, ".card-id", "height: var(--card-chip-height-mobile);"); + expectCssRuleToContain(mobileSection, ".card-id", "max-height: var(--card-chip-height-mobile);"); + expectCssRuleToContain(mobileSection, ".card-send-back", "height: 100%;"); + expectCssRuleToContain(mobileSection, ".card-send-back", "align-items: center;"); expectCssRuleToContain(mobileSection, ".card-send-back-btn", "line-height: 1;"); expectCssRuleToContain(mobileSection, ".card-menu-btn", "line-height: 1;"); expectCssRuleToContain(mobileSection, ".card-size-badge", "line-height: 1;"); @@ -507,6 +526,8 @@ describe("TaskCard badge wrapping (FN-5162)", () => { expectCssRuleToContain(menuTouchSection, ".card-menu-btn", "width: 28px;"); expectCssRuleToContain(menuTouchSection, ".card-menu-btn", "height: 28px;"); expectCssRuleToContain(menuTouchSection, ".card-menu-btn", "line-height: 1;"); + // Negative vertical margin cancels residual 28px layout contribution (same pattern as .card-edit-btn/.card-delete-btn). + expectCssRuleToContain(menuTouchSection, ".card-menu-btn", "margin: -6px 0;"); expectCssRuleToContain(menuTouchSection, ".card-menu-btn svg", "width: 16px;"); expectCssRuleToContain(menuTouchSection, ".card-menu-btn svg", "height: 16px;"); });