fix(dashboard): stack mobile Add comment above Refine/Proceed
Phone no longer pins Add comment under the action rail as a fixed bar. It uses the same full-width in-flow footer row as tablet, above Refine and Proceed. The composer stays fixed when open.
This commit is contained in:
@@ -1799,9 +1799,10 @@ FNXC:PlanningComments 2026-07-23-17:05:
|
||||
Desktop (≥1025px) keeps the in-document trigger under the plan markdown. Tablet and phone
|
||||
(≤1024px) use the action-rail counterpart so selection comments are not lost under the plan fold.
|
||||
|
||||
FNXC:PlanningComments 2026-07-24-05:35:
|
||||
Tablet (769–1024) places that rail control as a full-width row above Refine/Proceed. Phone
|
||||
(≤768) still lifts it into a fixed bottom bar above the mobile nav.
|
||||
FNXC:PlanningComments 2026-07-24-05:50:
|
||||
Phone and tablet both place that rail control as a full-width row above Refine/Proceed. A
|
||||
viewport-fixed bar above the mobile nav sat under the action buttons and required an awkward
|
||||
reach past Proceed — match the tablet in-flow stack on phone too.
|
||||
*/
|
||||
|
||||
.planning-comment-quote,
|
||||
@@ -1913,42 +1914,15 @@ plan actions, and a token-sized bottom inset keep all three controls inline with
|
||||
*/
|
||||
@media (max-width: 768px) {
|
||||
/*
|
||||
FNXC:PlanningComments 2026-07-23-17:05:
|
||||
On phone, pin the rail Add-comment control to the visual viewport above the mobile nav so a
|
||||
touch selection never requires scrolling the plan document or footer. Tablet (769–1024) keeps
|
||||
the in-flow full-width rail row from the 1024px block instead.
|
||||
|
||||
The plan-actions button rule forces width 100 percent. With position fixed that 100 percent is
|
||||
the viewport width, and combined with left/right insets the control overflowed past the right
|
||||
edge (measured 780px wide in a 768px viewport). Higher specificity + width auto lets left/right
|
||||
define the used width so the bar stays fully on-screen.
|
||||
FNXC:PlanningComments 2026-07-24-05:50:
|
||||
Phone keeps the ≤1024 in-flow full-width Add-comment row above Refine/Proceed (no fixed bar
|
||||
under the action rail). The composer still pins above the mobile nav so opening it after a mid-
|
||||
document selection does not require scrolling to the plan foot.
|
||||
*/
|
||||
.planning-plan-actions .btn.planning-add-comment--mobile {
|
||||
position: fixed;
|
||||
left: var(--space-md);
|
||||
right: var(--space-md);
|
||||
bottom: calc(
|
||||
var(--mobile-nav-height, 44px)
|
||||
+ max(env(safe-area-inset-bottom, 0px), 12px)
|
||||
+ var(--space-md)
|
||||
);
|
||||
z-index: var(--z-popover);
|
||||
width: auto;
|
||||
max-width: none;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.planning-comment-tray li {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:PlanningComments 2026-07-23-17:05:
|
||||
The comment composer used to flow at the end of the plan markdown, so opening it after a mid-
|
||||
document selection required scrolling to the document foot. Pin it to the visual viewport above
|
||||
the mobile nav/safe-area (and above the selection trigger slot) so Cancel / Add comment stay
|
||||
reachable without scrolling.
|
||||
*/
|
||||
.planning-comment-editor {
|
||||
position: fixed;
|
||||
left: var(--space-md);
|
||||
|
||||
@@ -3154,16 +3154,12 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
FN-8533 keeps the selection-adjacent control on wide desktop, but compact shells need a
|
||||
counterpart that cannot be lost under the document fold.
|
||||
|
||||
FNXC:PlanningComments 2026-07-23-17:05:
|
||||
On ≤768px the rail trigger is position:fixed above the mobile nav so a selection never
|
||||
requires scrolling.
|
||||
|
||||
FNXC:PlanningComments 2026-07-24-05:35:
|
||||
On tablet (769–1024) the same rail control stays in the plan action footer as a full-width
|
||||
row above Refine/Proceed. Document-level selectionchange still dismisses it when the
|
||||
selection collapses. CSS shows exactly one of the two variants; only established
|
||||
768px/1024px breakpoint literals are allowed here, while all other dimensions remain
|
||||
design-token based.
|
||||
FNXC:PlanningComments 2026-07-24-05:50:
|
||||
On tablet and phone (≤1024) the rail control stays in the plan action footer as a
|
||||
full-width row above Refine/Proceed so a selection never requires scrolling past the
|
||||
action baseline. Document-level selectionchange still dismisses it when the selection
|
||||
collapses. CSS shows exactly one of the two variants; only established 768px/1024px
|
||||
breakpoint literals are allowed here, while all other dimensions remain design-token based.
|
||||
*/}
|
||||
{selectedPlanQuote && !isCommentEditorOpen && (
|
||||
<button
|
||||
|
||||
@@ -170,18 +170,16 @@ describe("PlanningModeModal CSS responsive action contract", () => {
|
||||
const css = loadPlanningCss();
|
||||
const compactCss = getMediaBlocks(css, "@media (max-width: 1024px)").join("\n");
|
||||
const mobileCss = getMediaBlocks(css, MOBILE_ACTIONS_QUERY).join("\n");
|
||||
const tabletRailTriggerRule = findRule(compactCss, ".planning-plan-actions .btn.planning-add-comment--mobile");
|
||||
const mobileTriggerRule = findRule(mobileCss, ".planning-plan-actions .btn.planning-add-comment--mobile");
|
||||
const railTriggerRule = findRule(compactCss, ".planning-plan-actions .btn.planning-add-comment--mobile");
|
||||
const mobileEditorRule = findRule(mobileCss, ".planning-comment-editor");
|
||||
|
||||
expect(findRule(css, ".planning-add-comment--mobile")).toMatch(/display\s*:\s*none\s*;/);
|
||||
expect(findRule(compactCss, ".planning-add-comment--document")).toMatch(/display\s*:\s*none\s*;/);
|
||||
expect(tabletRailTriggerRule).toMatch(/display\s*:\s*flex\s*;/);
|
||||
expect(tabletRailTriggerRule).toMatch(/grid-column\s*:\s*1\s*\/\s*-1\s*;/);
|
||||
expect(tabletRailTriggerRule).toMatch(/margin-top\s*:\s*0\s*;/);
|
||||
expect(mobileTriggerRule).toMatch(/position\s*:\s*fixed\s*;/);
|
||||
expect(mobileTriggerRule).toMatch(/width\s*:\s*auto\s*;/);
|
||||
expect(mobileTriggerRule).toMatch(/var\(--mobile-nav-height/);
|
||||
expect(railTriggerRule).toMatch(/display\s*:\s*flex\s*;/);
|
||||
expect(railTriggerRule).toMatch(/grid-column\s*:\s*1\s*\/\s*-1\s*;/);
|
||||
expect(railTriggerRule).toMatch(/margin-top\s*:\s*0\s*;/);
|
||||
// FNXC:PlanningComments 2026-07-24-05:50: phone no longer overrides the rail trigger to fixed.
|
||||
expect(findRule(mobileCss, ".planning-plan-actions .btn.planning-add-comment--mobile")).toBeUndefined();
|
||||
expect(mobileEditorRule).toMatch(/position\s*:\s*fixed\s*;/);
|
||||
expect(mobileEditorRule).toMatch(/var\(--mobile-nav-height/);
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ describe("PlanningModeModal sequential layout", () => {
|
||||
expect(css).toMatch(/\.planning-add-comment--mobile\s*\{[^}]*display\s*:\s*none\s*;/);
|
||||
expect(css).toMatch(/@media \(max-width: 1024px\)[\s\S]*?\.planning-add-comment--document\s*\{[^}]*display\s*:\s*none\s*;/);
|
||||
expect(css).toMatch(/@media \(max-width: 1024px\)[\s\S]*?\.planning-plan-actions \.btn\.planning-add-comment--mobile\s*\{[^}]*display\s*:\s*flex\s*;[^}]*grid-column\s*:\s*1\s*\/\s*-1\s*;/);
|
||||
expect(css).toMatch(/@media \(max-width: 768px\)[\s\S]*?\.planning-plan-actions \.btn\.planning-add-comment--mobile\s*\{[^}]*position\s*:\s*fixed\s*;[^}]*width\s*:\s*auto\s*;/);
|
||||
expect(css).not.toMatch(/@media \(max-width: 768px\)[\s\S]*?\.planning-plan-actions \.btn\.planning-add-comment--mobile\s*\{[^}]*position\s*:\s*fixed\s*;/);
|
||||
expect(css).toMatch(/@media \(max-width: 768px\)[\s\S]*?\.planning-comment-editor\s*\{[^}]*position\s*:\s*fixed\s*;/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -236,9 +236,9 @@ describe.runIf(executablePath)("Planning Mode browser E2E", () => {
|
||||
Start at the preceding tab stop, then send actual Tab keys. Programmatic focus alone would
|
||||
incorrectly accept a tabIndex=-1 contextual-comment trigger as keyboard reachable.
|
||||
|
||||
FNXC:PlanningComments 2026-07-24-05:35:
|
||||
Phone keeps the trigger position:fixed above the nav; tablet keeps it in the action rail as
|
||||
a full-width row above Refine/Proceed. Both stay in the actions DOM for focus order.
|
||||
FNXC:PlanningComments 2026-07-24-05:50:
|
||||
Phone and tablet keep the trigger in the action rail as a full-width row above
|
||||
Refine/Proceed (not a fixed bar under the actions).
|
||||
*/
|
||||
focusable[triggerIndex - 1]?.focus();
|
||||
return {
|
||||
@@ -274,11 +274,12 @@ describe.runIf(executablePath)("Planning Mode browser E2E", () => {
|
||||
hiddenButtonsTabbable: 0,
|
||||
});
|
||||
if (inFooter) {
|
||||
expect(placement.actionLabels).toEqual(expect.arrayContaining(["Refine", "Proceed with plan"]));
|
||||
if (!positionFixed) {
|
||||
// Tablet in-flow rail row: Add comment sits above Refine/Proceed in the same footer.
|
||||
expect(placement.actionLabels).toEqual(expect.arrayContaining(["Add comment to selection"]));
|
||||
}
|
||||
// In-flow rail row: Add comment sits above Refine/Proceed in the same footer.
|
||||
expect(placement.actionLabels).toEqual(expect.arrayContaining([
|
||||
"Add comment to selection",
|
||||
"Refine",
|
||||
"Proceed with plan",
|
||||
]));
|
||||
} else {
|
||||
expect(placement.actionLabels).not.toContain("Add comment to selection");
|
||||
}
|
||||
@@ -314,9 +315,9 @@ describe.runIf(executablePath)("Planning Mode browser E2E", () => {
|
||||
expect(afterOpen).toMatchObject({
|
||||
addCommentButtons: 0,
|
||||
actionChildren: 0,
|
||||
// Phone pins the composer; tablet/desktop keep the in-document editor.
|
||||
editorPosition: positionFixed ? "fixed" : "static",
|
||||
editorInsideViewport: positionFixed ? true : expect.any(Boolean),
|
||||
// Phone pins the composer when open; tablet/desktop keep the in-document editor.
|
||||
editorPosition: viewport.width <= 768 && inFooter ? "fixed" : "static",
|
||||
editorInsideViewport: viewport.width <= 768 && inFooter ? true : expect.any(Boolean),
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
@@ -334,9 +335,8 @@ describe.runIf(executablePath)("Planning Mode browser E2E", () => {
|
||||
|
||||
it("places the sole contextual comment trigger by viewport in embedded and modal Planning", async () => {
|
||||
for (const presentation of ["embedded", "modal"] as const) {
|
||||
// Phone: fixed bar above nav.
|
||||
await verifyContextualCommentPlacement({ width: 768, height: 900 }, { inFooter: true, positionFixed: true }, presentation);
|
||||
// Tablet: full-width action-rail row above Refine/Proceed.
|
||||
// Phone + tablet: full-width action-rail row above Refine/Proceed.
|
||||
await verifyContextualCommentPlacement({ width: 768, height: 900 }, { inFooter: true, positionFixed: false }, presentation);
|
||||
await verifyContextualCommentPlacement({ width: 769, height: 900 }, { inFooter: true, positionFixed: false }, presentation);
|
||||
await verifyContextualCommentPlacement({ width: 1024, height: 900 }, { inFooter: true, positionFixed: false }, presentation);
|
||||
// Desktop: document-adjacent trigger only.
|
||||
|
||||
Reference in New Issue
Block a user