Félix Malfait 2d9582117b feat(front): preview highlighted record in the search command bar (#23413)
Previewing the highlighted record while searching. The preview is a card
anchored to the highlighted result, and its fields come from the
object's **index view** (the base list view), so what you see while
searching matches the list you came from.

## Screenshots

> Note: these were taken before the latest design pass (smaller header,
field cap). The layout is otherwise unchanged.

Highlighted result, showing the index view's columns:

![Preview
tooltip](https://raw.githubusercontent.com/twentyhq/twenty/b39dbeea8c525581a14f34035037ad9ba79999df/.pr-screenshots/preview-tooltip-collapsed.png)

Overflow and hidden columns sit behind the `More (N)` expander:

![Preview tooltip
expanded](https://raw.githubusercontent.com/twentyhq/twenty/b39dbeea8c525581a14f34035037ad9ba79999df/.pr-screenshots/preview-tooltip-expanded.png)

Arrowing to a Person re-anchors the card and renders that object's own
index view fields:

![Preview tooltip for a
person](https://raw.githubusercontent.com/twentyhq/twenty/b39dbeea8c525581a14f34035037ad9ba79999df/.pr-screenshots/preview-tooltip-person.png)

## Changes

- `SidePanelSearchRecordPreviewCard.tsx` — the card: the shared
`SidePanelPageInfoLayout` header (avatar + name + created-at), then
read-only `FieldDisplay` rows, then the `More (N)` expander.
- `useSidePanelSearchRecordPreviewFields.ts` — resolves the object's
index view through `useViewOrDefaultView` and splits its `viewFields`
into visible and hidden, sorted by position, dropping the label
identifier since the card already shows the record name.
- `useSidePanelSearchRecordPreviewItem.ts` — resolves the highlighted
item from the selectable list, following the selection immediately.
- `useSidePanelSearchRecordPreviewRecord.ts` — hydrates the record into
the record store so field displays can read their values. The fetch is
debounced 200ms so holding an arrow key doesn't fire a `findOne` per row
crossed, and it reports whether the record is hydrated yet.
- `SidePanelSearchRecordsPage.tsx` — anchors the card with `AppTooltip`
(`place="left-start"`, controlled `isOpen`, `clickable` so the expander
is reachable) against a per-result anchor id.

## How many fields show

Collapsed, the card shows at most seven of the index view's visible
columns. Everything past that, plus the columns hidden in that view,
sits behind `More (N)`. So the expander appears whenever there are more
fields than fit, not only when the view happens to have hidden columns.

## Keeping the card stable while it loads

The first cut jumped: measuring it over time gave `288px → unmounted for
~200ms → 232px`. Two separate causes, both fixed.

It was **unmounting between records** because the previewed item was
debounced and briefly resolved to `null`. The selection is now followed
immediately and the *fetch* is what's debounced instead, so the card is
reused across records rather than remounted.

Its **size was derived from the data**, so every value that arrived
nudged the layout. The header, rows (24px) and width are fixed, with
skeleton placeholders for values until the record is hydrated. The field
list comes from view metadata, which is available synchronously, so the
card is its final size on first paint.

Measured after that change: a constant `360x328` across 13 consecutive
records spanning Person and Workspace Member, and no unmount. The
skeleton and loaded states are the same height, so values just fade in.

The card still disappears briefly on a brand-new search. That tracks the
results list turning over — the anchor row it attaches to is genuinely
removed from the DOM — so following the list is the correct behaviour
there rather than holding a stale card against a deleted anchor.

## Notes

The preview is read-only (`FieldDisplay`, not `RecordInlineCell`) — a
floating preview isn't the right place to start an inline edit, and it
keeps the card out of the field hover/edit portal machinery.

The `More` button is wrapped in a container that prevents the default
mousedown focus shift. Without it, clicking the expander moved focus out
of the search input and arrow keys started driving the record table
behind the panel instead of the results list.

The section heading still reads `Results`; the design says `Records`.
Left as-is since it is out of scope here.

## Testing

- `nx typecheck twenty-front` passes.
- `oxlint --type-aware` and `oxfmt` clean on the changed files.
- Verified manually against a seeded dev workspace: anchoring and
re-anchoring on arrow navigation, Company vs Person rendering their own
index view fields, the `More` expander, arrow keys still driving the
results list after clicking it, and the card holding a constant size
through load.
2026-07-28 22:42:19 +02:00
2026-06-11 11:02:28 +02:00

Twenty logo

The #1 Open-Source CRM

Website · Documentation · Roadmap · Discord · Figma

Twenty banner


Why Twenty

Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.

Learn more about why we built Twenty


Installation

Cloud

The fastest way to get started. Sign up at twenty.com and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.

Build an app

Scaffold a new app with the Twenty CLI:

npx create-twenty-app my-app

Define objects, fields, and views as code:

import { defineObject, FieldType } from 'twenty-sdk/define';

export default defineObject({
  nameSingular: 'deal',
  namePlural: 'deals',
  labelSingular: 'Deal',
  labelPlural: 'Deals',
  fields: [
    { name: 'name', label: 'Name', type: FieldType.TEXT },
    { name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
    { name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
  ],
});

Then ship it to your workspace:

npx twenty app:publish --private

See the app development guide for objects, views, agents, and logic functions.

Self-hosting

Run Twenty on your own infrastructure with Docker Compose, or contribute locally via the local setup guide.



Everything you need

Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.

Want to go deeper? Read the User Guide for product walkthroughs, or the Documentation for developer reference.

Create your apps

Learn more about apps in doc

Stay on top with version control

Learn more about version control in doc

All the tools you need to build anything

Learn more about primitives in doc

Customize your layouts

Learn more about layouts in doc

AI agents and chats

Learn more about AI in doc

Plus all the tools of a good CRM

Learn more about CRM features in doc


Stack

Thanks

Greptile      Sentry      Crowdin

Thanks to these amazing services that we use and recommend for code review (Greptile), catching bugs (Sentry) and translating (Crowdin).

Join the Community

Star the repo · Discord · Feature requests · Releases · X · LinkedIn · Crowdin · Contribute

S
Description
The open alternative to Salesforce, designed for AI.
Readme AGPL-3.0 1.4 GiB
Languages
TypeScript 79.6%
MDX 17.3%
JavaScript 2.7%
Python 0.2%
SCSS 0.1%