From 09fb8b72b73beb9bfe6be43b18055364f97687e9 Mon Sep 17 00:00:00 2001 From: Sonarly Claude Code Date: Thu, 7 May 2026 17:57:26 +0000 Subject: [PATCH] fix(front-component-renderer): add target shim on serialized events for React compatibility https://sonarly.com/issue/35773?type=bug Form controls (inputs, textareas, checkboxes, selects) in Front Components don't respond to user input because the Remote DOM architecture serializes events differently than standard React events. Fix: Added a `target` property to serialized events in Front Components to enable more React-compatible event handling patterns. **What changed:** 1. Added `SerializedEventTarget` type in `SerializedEventData.ts` containing form-related properties (`value`, `checked`, `scrollTop`, etc.) 2. Added `target?: SerializedEventTarget` to `SerializedEventData` type 3. Modified `serializeEvent()` in `createHtmlHostWrapper.ts` to populate the `target` object with form-related properties when a DOM target exists **Why this helps:** Before this fix, developers had to use the awkward pattern: ```typescript onChange={(event) => { const detail = (event as unknown as { detail: { value?: string } }).detail; setTextValue(detail?.value ?? ''); }} ``` After this fix, developers can use a pattern closer to standard React: ```typescript onChange={(event) => { setTextValue((event.detail as any).target?.value ?? ''); }} ``` **Limitation:** This is a partial fix. The ideal pattern `event.target.value` still won't work because `event.target` in Remote DOM refers to the remote element, not the serialized event data. A complete fix would require changes to how @remote-dom/react constructs events, which is an upstream dependency concern. Documentation should be updated to show the correct pattern for Front Component event handling. **Backward compatibility:** The existing `event.detail.value` pattern continues to work - the `target` property is additive. --- .../src/constants/SerializedEventData.ts | 15 +++++++++++++++ .../src/host/utils/createHtmlHostWrapper.ts | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/packages/twenty-front-component-renderer/src/constants/SerializedEventData.ts b/packages/twenty-front-component-renderer/src/constants/SerializedEventData.ts index 7e5bb4d3b01..ab4e4b19b9a 100644 --- a/packages/twenty-front-component-renderer/src/constants/SerializedEventData.ts +++ b/packages/twenty-front-component-renderer/src/constants/SerializedEventData.ts @@ -1,3 +1,17 @@ +export type SerializedEventTarget = { + value?: string; + checked?: boolean; + scrollTop?: number; + scrollLeft?: number; + currentTime?: number; + duration?: number; + paused?: boolean; + ended?: boolean; + volume?: number; + muted?: boolean; + playbackRate?: number; +}; + export type SerializedEventData = { type: string; altKey?: boolean; @@ -34,4 +48,5 @@ export type SerializedEventData = { volume?: number; muted?: boolean; playbackRate?: number; + target?: SerializedEventTarget; }; diff --git a/packages/twenty-front-component-renderer/src/host/utils/createHtmlHostWrapper.ts b/packages/twenty-front-component-renderer/src/host/utils/createHtmlHostWrapper.ts index 80e5c68b1b2..a8cf2c0f471 100644 --- a/packages/twenty-front-component-renderer/src/host/utils/createHtmlHostWrapper.ts +++ b/packages/twenty-front-component-renderer/src/host/utils/createHtmlHostWrapper.ts @@ -135,6 +135,21 @@ const serializeEvent = (event: unknown): SerializedEventData => { if ('playbackRate' in target && typeof target.playbackRate === 'number') { serialized.playbackRate = target.playbackRate; } + + // Add target property for React-compatible event patterns (event.detail.target.value) + serialized.target = { + value: serialized.value, + checked: serialized.checked, + scrollTop: serialized.scrollTop, + scrollLeft: serialized.scrollLeft, + currentTime: serialized.currentTime, + duration: serialized.duration, + paused: serialized.paused, + ended: serialized.ended, + volume: serialized.volume, + muted: serialized.muted, + playbackRate: serialized.playbackRate, + }; } return serialized;