Fixes: #21243 # Issue When a user is on a specific record's page (for example, looking at a Person) and opens a related record (like a Note) in the right-side panel, clicking "Permanently Delete" on that Note would abruptly redirect the user to the main "Notes" list. This breaks the user's workflow, as they typically want to remain on the parent Person page after deleting a sub-record. # Root Cause Inside the `DestroyRecordsCommand` and `DeleteRecordsCommand` components, the application was programmed to unconditionally trigger a `navigateApp` redirect to the deleted object's index page upon successful deletion. The code did not account for whether the deletion was triggered from the main index page or from inside a contextual side panel. # How we fixed it I introduced a new `isInSidePanel` flag into the `HeadlessCommandContextApi`. The command menu now detects if the delete action originated from inside a side panel and passes this flag down the execution chain. If `true`, the `DestroyRecordsCommand` simply closes the panel (`closeSidePanelMenu()`) and keeps the user exactly where they were. ## After that fix, I encountered another issue (UI not updating automatically) Because the app now correctly kept the user on the Person page, a new bug surfaced: the "Note" chip inside the relation table did not disappear immediately. The user had to manually refresh the page to see the deletion. This happened because: 1. The Apollo optimistic cache occasionally failed to trace deeply nested morph-relationships back to the parent. 2. A standard local React `useState` fallback was insufficient. The table component aggressively unmounts and remounts relation cells whenever a user hovers over them to display interactive controls, which would wipe the local React state clean and cause the "ghost chip" to reappear. ##How I fixed that issue (and optimized it) I built an event-driven fallback using global Jotai state to permanently hide the chips: 1. **Precision ID Broadcasting**: I updated the `useDestroyManyRecords` and `useIncrementalDestroyManyRecords` hooks to extract and broadcast only the *confirmed* destroyed IDs directly from the Apollo mutation response, preventing false-positive UI removals if the backend performed a partial delete. 2. **Batch Optimizations**: During bulk deletions, events are now broadcasted per-batch rather than accumulating thousands of IDs in memory until the end, keeping the UI instantly responsive and memory bounded. 3. **Per-Cell State Scoping (`atomFamily`)**: Instead of a leaky global array, we used Jotai's `atomFamily` to dynamically generate a unique Noticeboard for every specific table cell (`${recordId}-${fieldName}`). This ensures the hidden-state survives mouse-hover unmounts while remaining cleanly isolated. 4. **Defensive Filtering**: The `RelationFromManyFieldDisplay` component was updated to defensively guard against `undefined` array entries and strictly match deleted IDs only against valid foreign keys (ending in `'Id'`), preventing false-positive removals if a UUID happened to be pasted into a description field. 5. **SSE Resilience**: We hardened the Server-Sent Event (SSE) listeners with optional chaining and null-filtering to ensure malformed backend payloads do not crash the real-time event pipeline. # Screen Recording https://github.com/user-attachments/assets/19fc8a3f-ba28-43c2-b1f3-a91127cdad97 --------- Co-authored-by: bosiraphael <raphael.bosi@gmail.com> Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
The #1 Open-Source CRM
Website ·
Documentation ·
Roadmap ·
Discord ·
Figma
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.
|
|
|
|
|
|
Stack
TypeScript
Nx
NestJS, with BullMQ,
PostgreSQL,
Redis
React, with Jotai, Linaria and Lingui
Thanks
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





