From 5f3f734b34777ae29c777e1fb5c0062544f0e188 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:51:03 +0200 Subject: [PATCH] fix(front): page header title overlap, Cmd+K on page layout pages & stable tooltip id (#22678) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Three independent front-end fixes. ### 1. Settings page header title overlaps the breadcrumb On settings detail pages (e.g. an app's logic function), a long centered title visually overlapped the breadcrumb. `PageCardHeader` renders the header as a CSS grid (`minmax(0, 1fr) auto minmax(0, 1fr)`) and the centered title used `justify-self: center`. With grid, `justify-self: center` sizes the item to its own content width (up to its `max-width`) instead of to its grid track, so a long title grew wider than the center track and spilled sideways over the breadcrumb column — its `overflow: hidden` only clipped its own children to that oversized box, not to the track. Fix: let the centered title fill and shrink to its grid track so it clips (with ellipsis) inside its own column instead of overflowing into the breadcrumb. - Center column: `auto` → `minmax(0, auto)` so it can shrink when space is tight. - Centered title: `justify-self: center` → `justify-self: stretch` + `min-width: 0`. ### 2. Cmd+K does nothing on standalone page layout pages On standalone page layout pages (`/page/:pageLayoutId`, used to render app front components), the command menu shortcut (Cmd+K) did nothing. Cmd+K is a global hotkey with a modifier, so it only runs when the active focus-stack config has `enableGlobalHotkeysWithModifiers: true`. `RecordIndexPage` and `RecordShowPage` explicitly reset the focus stack to enable it, but `PageChangeEffect` had no case for `AppPath.PageLayoutPage`, so the stack kept a stale config (commonly the Settings config, which disables modifier hotkeys) and swallowed the shortcut. Fix: add a `PageLayoutPage` case in `PageChangeEffect` that resets the focus stack with modifier hotkeys enabled (mirroring `RecordShowPage`), plus a new `PageFocusId.PageLayoutPage` value. ### 3. Ever-changing / unstable tooltip element id `OverflowingTextWithTooltip` built its element id from `title-id-${+new Date()}`, so a new id (the current epoch time in ms) was generated on every render — the id visibly kept increasing in the DOM. This is unstable (the tooltip anchor `#id` churns on each render) and collision-prone (two tooltips rendering in the same millisecond get the same id, producing duplicate DOM ids and an ambiguous anchor). Fix: derive the id from React's `useId()` so it is stable per instance and unique. The colons `useId()` produces are stripped, since the id is used inside a CSS selector (`anchorSelect={#${id}}`) where colons are invalid. ## Test plan - [ ] Open a settings detail page with a long title (e.g. an app logic function named `maintain-account-team-member-name-on-created`) and confirm the title no longer overlaps the breadcrumb, and truncates with an ellipsis when space is tight. - [ ] Navigate to a standalone page layout page (an app's front-component page) and confirm Cmd+K opens the command menu, including after coming from Settings. - [ ] Inspect an overflowing title/tooltip in DevTools and confirm its `id` stays stable across re-renders (no longer increments), and tooltips still show on hover of truncated text. --- .../app/effect-components/PageChangeEffect.tsx | 16 ++++++++++++++++ .../src/modules/types/PageFocusId.ts | 1 + .../ui/layout/page/components/PageCardHeader.tsx | 5 +++-- .../OverflowingTextWithTooltip.tsx | 10 ++++++++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx index 4c565aab1d..6e277d833a 100644 --- a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx @@ -227,6 +227,22 @@ export const PageChangeEffect = () => { } break; } + case isMatchingLocation(location, AppPath.PageLayoutPage): { + resetFocusStackToFocusItem({ + focusStackItem: { + focusId: PageFocusId.PageLayoutPage, + componentInstance: { + componentType: FocusComponentType.PAGE, + componentInstanceId: PageFocusId.PageLayoutPage, + }, + globalHotkeysConfig: { + enableGlobalHotkeysWithModifiers: true, + enableGlobalHotkeysConflictingWithKeyboard: true, + }, + }, + }); + break; + } case isMatchingLocation(location, AppPath.SignInUp): { resetFocusStackToFocusItem({ focusStackItem: { diff --git a/packages/twenty-front/src/modules/types/PageFocusId.ts b/packages/twenty-front/src/modules/types/PageFocusId.ts index c2e739fefd..3956eb9738 100644 --- a/packages/twenty-front/src/modules/types/PageFocusId.ts +++ b/packages/twenty-front/src/modules/types/PageFocusId.ts @@ -8,4 +8,5 @@ export enum PageFocusId { PlanRequired = 'plan-required', RecordShowPage = 'record-show-page', RecordIndex = 'record-index', + PageLayoutPage = 'page-layout-page', } diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/PageCardHeader.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/PageCardHeader.tsx index 9f598b0f80..c82ae45c50 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/PageCardHeader.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/PageCardHeader.tsx @@ -32,7 +32,7 @@ const StyledHeader = styled.div<{ centerTitle?: boolean }>` display: grid; grid-template-columns: ${({ centerTitle }) => centerTitle - ? 'minmax(0, 1fr) auto minmax(0, 1fr)' + ? 'minmax(0, 1fr) minmax(0, auto) minmax(0, 1fr)' : 'minmax(0, auto) minmax(0, 1fr)'}; min-height: ${SIDE_PANEL_TOP_BAR_HEIGHT}px; padding: 0 ${themeCssVariables.spacing[3]}; @@ -63,8 +63,9 @@ const StyledTitle = styled.div<{ titleColor?: string }>` const StyledCenteredTitle = styled(StyledTitle)` grid-column: 2; justify-content: center; - justify-self: center; + justify-self: stretch; max-width: 100%; + min-width: 0; overflow: hidden; `; diff --git a/packages/twenty-ui/src/surfaces/OverflowingTextWithTooltip/OverflowingTextWithTooltip.tsx b/packages/twenty-ui/src/surfaces/OverflowingTextWithTooltip/OverflowingTextWithTooltip.tsx index 136ae9707b..f9db9d040c 100644 --- a/packages/twenty-ui/src/surfaces/OverflowingTextWithTooltip/OverflowingTextWithTooltip.tsx +++ b/packages/twenty-ui/src/surfaces/OverflowingTextWithTooltip/OverflowingTextWithTooltip.tsx @@ -1,4 +1,10 @@ -import { type CSSProperties, type ReactNode, useRef, useState } from 'react'; +import { + type CSSProperties, + type ReactNode, + useId, + useRef, + useState, +} from 'react'; import { createPortal } from 'react-dom'; import { isNonEmptyString } from '@sniptt/guards'; @@ -35,7 +41,7 @@ export const OverflowingTextWithTooltip = ({ tooltipDelay = TooltipDelay.mediumDelay, alwaysShowTooltip = false, }: OverflowingTextWithTooltipProps) => { - const textElementId = `title-id-${+new Date()}`; + const textElementId = `title-id-${useId().replace(/:/g, '')}`; const textRef = useRef(null);