fix(dashboard): stop Chat Latest button jumping under the cursor
Center the jump-to-latest chip with margin-inline auto instead of transform:translateX(-50%) so global .btn transform transitions and :active scale cannot shift it sideways in Quick Chat and full Chat.
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix the Chat View "Latest" button shifting sideways out from under the cursor when clicked.
|
||||
summary: Stop the Chat/Quick Chat "Latest" button from jumping when the cursor moves near or presses it.
|
||||
category: fix
|
||||
dev: `.chat-jump-to-latest:active` now composes `translateX(-50%) scale(0.97)` so the global `.btn:active` transform no longer replaces the centering transform.
|
||||
dev: Center `.chat-jump-to-latest` with left/right + margin-inline auto instead of transform:translateX(-50%) so global `.btn` transform transitions and :active scale cannot shift the chip sideways.
|
||||
|
||||
@@ -1035,20 +1035,21 @@ The thread-wide Markdown/plain render toggle (`.chat-thread-header-render-toggle
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.chat-jump-to-latest {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: calc(var(--space-xl) * 3);
|
||||
transform: translateX(-50%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:ChatView 2026-07-22-00:00:
|
||||
The Latest button is centered with transform: translateX(-50%), so the global .btn:active { transform: scale(0.97) } rule must not replace that transform wholesale — doing so shifted the button half its width sideways on mousedown, out from under the cursor mid-click. Compose both transforms on :active instead (same fix as .devserver-log-viewer__new-logs-button).
|
||||
|
||||
FNXC:ChatView 2026-07-23-18:15:
|
||||
Compose-on-:active was still fragile: same specificity as `.btn:active` can lose the cascade war (SelectionCommentPopover regressed the same way), and the shared `.btn { transition: transform }` made any transform churn feel like the chip jumps when the cursor moves near/over it in Quick Chat and full Chat. Center with left/right + margin-inline auto + width: fit-content so positioning no longer owns `transform`. Global `.btn:active` scale then applies in place around the geometric center without a sideways teleport. Covers session + room jump buttons (shared class), floating Quick Chat, main Chat view, and mobile.
|
||||
*/
|
||||
.chat-jump-to-latest:active {
|
||||
transform: translateX(-50%) scale(0.97);
|
||||
.chat-jump-to-latest {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: fit-content;
|
||||
margin-inline: auto;
|
||||
bottom: calc(var(--space-xl) * 3);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.chat-message--user {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
/*
|
||||
FNXC:ChatView 2026-07-23-18:15:
|
||||
Regression guard for the Latest jump chip teleporting under the cursor. The control is shared by
|
||||
main Chat, floating Quick Chat, and room threads (`.chat-jump-to-latest`). Centering must not use
|
||||
`transform: translateX(-50%)` because global `.btn:active { transform: scale(0.97) }` and
|
||||
`.btn { transition: transform }` replace or animate that translate and shift the chip sideways when
|
||||
the pointer approaches or presses it. jsdom cannot hit-test the mid-press jump, so the invariant is
|
||||
asserted on the stylesheet: every transform rule that targets the jump class is forbidden, and the
|
||||
base rule must center with left/right + margin-inline auto + width: fit-content.
|
||||
*/
|
||||
const chatViewCss = readFileSync(resolve(__dirname, "../ChatView.css"), "utf8");
|
||||
|
||||
function uncomment(css: string): string {
|
||||
return css.replace(/\/\*[\s\S]*?\*\//g, "");
|
||||
}
|
||||
|
||||
function collectJumpBlocks(css: string): Array<{ selector: string; block: string }> {
|
||||
const blocks: Array<{ selector: string; block: string }> = [];
|
||||
const rulePattern = /([^{}]+)\{([^{}]*)\}/g;
|
||||
for (const match of uncomment(css).matchAll(rulePattern)) {
|
||||
const selector = match[1].trim();
|
||||
if (selector.split(",").some((part) => part.trim().includes(".chat-jump-to-latest"))) {
|
||||
blocks.push({ selector, block: match[2] });
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
describe("ChatView jump-to-latest centering", () => {
|
||||
it("centers without transform so btn press/hover transitions cannot shift the chip sideways", () => {
|
||||
const blocks = collectJumpBlocks(chatViewCss);
|
||||
expect(blocks.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const base = blocks.find(({ selector }) => selector.trim() === ".chat-jump-to-latest");
|
||||
expect(base, "base .chat-jump-to-latest rule must exist").toBeDefined();
|
||||
expect(base!.block).toMatch(/left\s*:\s*0/);
|
||||
expect(base!.block).toMatch(/right\s*:\s*0/);
|
||||
expect(base!.block).toMatch(/margin-inline\s*:\s*auto/);
|
||||
expect(base!.block).toMatch(/width\s*:\s*fit-content/);
|
||||
expect(base!.block).not.toMatch(/transform\s*:/);
|
||||
expect(base!.block).not.toMatch(/translateX\s*\(/);
|
||||
|
||||
for (const { selector, block } of blocks) {
|
||||
expect(
|
||||
block,
|
||||
`jump rule "${selector}" must not own transform (global .btn:active/hover transitions own it)`,
|
||||
).not.toMatch(/transform\s*:/);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user