DeviSriSaiCharan 947a4d5253 Fix: prevent unexpected navigation when destroying record from side panel (#21391)
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>
2026-06-11 15:40:56 +00:00
2026-05-21 13:35:35 +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

Description
No description provided
Readme AGPL-3.0 1.8 GiB
Languages
TypeScript 78.3%
MDX 18.2%
JavaScript 3%
Python 0.2%
SCSS 0.1%