- Batch Gmail API calls using `googleapis-batcher` for folder processing
- Add concurrency limit for Microsoft Graph folder processing
- Skip IMAP folder sync when no new messages (checks UIDVALIDITY/MODSEQ)
- Refactored `syncMessageFolders` to return folder state directly,
avoiding extra DB round-trips
- Refactored `processPendingFolderActions` to reuse state instead of
querying DB again
- Add unique index on message folders entity
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Description
This PR fixes an issue where the GraphQL schema was being incorrectly
cached and shared across different API keys within the same workspace.
This resulted in the `createdBy` field (Actor) from the first API key's
request being erroneously attributed to subsequent requests made by
different API keys.
## Changes
- Updated the `@graphql-yoga/nestjs` patch to include the request's
`Authorization` header in the schema cache key generation logic.
- This ensures that every unique authentication token (and thus every
unique API key) generates a distinct cache entry, preventing schema
context collisions.
Closes#15093
## Summary
- Add code interpreter tool that enables AI to execute Python code for
data analysis, CSV processing, and chart generation
- Support for both local (development) and E2B (sandboxed production)
execution drivers
- Real-time streaming of stdout/stderr and generated files
- Frontend components for displaying code execution results with
expandable sections
## Code Quality Improvements
- Extract `getMimeType` to shared utility to reduce code duplication
between drivers
- Fix security issue: escape single quotes/backslashes in E2B driver env
variable injection
- Add `buildExecutionState` helper to reduce duplicated state object
construction
- Add `DEFAULT_CODE_INTERPRETER_TIMEOUT_MS` constant for consistency
- Fix lingui linting warning and TypeScript theme errors in frontend
## Test Plan
- [ ] Test code interpreter with local driver in development
- [ ] Test code interpreter with E2B driver in production environment
- [ ] Verify streaming output displays correctly in chat UI
- [ ] Verify generated files (charts, CSVs) are uploaded and
downloadable
- [ ] Test file upload flow (CSV, Excel) triggers code interpreter
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Updates generated i18n catalogs for Polish and pseudo-English, adding
strings for code execution/output (code interpreter) and various UI
messages, with minor text adjustments.
>
> - **Localization**:
> - **Generated catalogs**: Refresh `locales/generated/pl-PL.ts` and
`locales/generated/pseudo-en.ts`.
> - Add strings for code execution/output (e.g., code, copy code/output,
running/waiting states, download files, generated files, Python code
execution).
> - Include new UI texts (errors, prompts, menus) and minor text
corrections.
> - No changes to `pt-BR`; other files unchanged functionally.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
befc13d02c21e5a6647bc1aa6daa2a89f60b7ef8. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Bumps
[@types/unzipper](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/unzipper)
from 0.10.10 to 0.10.11.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/unzipper">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
I was looking into [Dependabot Alert
107](https://github.com/twentyhq/twenty/security/dependabot/107) and
figured that the alert is caused by `vite-plugin-dts`, which is a
development dependency and does not make it into the production build
for it to be dangerous.
However, while at it, I also saw that some packages used plugins from
root package.json while others had them defined in their local
package.json. Therefore, I refactored to move plugins where they're
required and removed a redundant package.
Builds for the following succeed as intended:
- twenty-ui
- twenty-emails
- twenty-website
- twenty-front
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Resolves [Dependabot Alert
323](https://github.com/twentyhq/twenty/security/dependabot/323),
[Dependabot Alert
324](https://github.com/twentyhq/twenty/security/dependabot/324) and
[Dependabot Alert
325](https://github.com/twentyhq/twenty/security/dependabot/325).
It updates Sentry's packages on the server from 10.21.0 to 10.27.0.
I also moved @sentry/react to twenty-front package.json and updated the
version from 9.26.0 to 10.27.0 - no breaking changes were introduced in
the major upgrade in regards to the API exposed by the dependency.
Since @sentry/profiling-node was redundant in the root package.json, I
removed it - twenty-server has it already and is the only package
dependent on @sentry/profiling-node.
Resolves [Dependabot Alert
74](https://github.com/twentyhq/twenty/security/dependabot/74).
Upgraded `graphql-upload` from `13.0.0` to `16.0.2`. Type exports
changed. API remains the same.
Tested the following upload flows:
- profile picture
- workspace logo
- rich text editor (image, video, file)
- record profile picture
- file associated to record.
They all work as intended, nothing breaks.
There was an extra definition of @nx/js inside twenty-server, which was
not recognized during upgrade by the CLI. This caused two versions of
@nx/js in the repo - 22.0.3 from root and 21.3.11 from the twenty-server
package.
Removed the one inside twenty-server to maintain a single source of
truth.
Some Glob CLI related alerts were generated last night. I believe
they're safe to dismiss since I do not expect us to use the Glob CLI in
production environment, and those alerts do not impact the API, but
still updating the dependency version just in case.
Not sure if this would resolve all/any of the alerts since glob is a
dependency for many other dependencies, so a good number of dependency
variants are pulled in.
The alert that confirms it's just a CLI related vulnerability:
[Dependabot Alert
307](https://github.com/twentyhq/twenty/security/dependabot/307)
Resolves [Dependabot Alert
309](https://github.com/twentyhq/twenty/security/dependabot/309) and
maybe also a few others.
Bumped up the version for js-yaml to 4.1.1 and 3.14.2 across transitive
dependencies using `yarn up js-yaml --recursive`.
- Allow widgets to be hidden based on device's type conditions: desktop
or mobile
- Create two widgets for rich text fields on tasks and notes. One widget
is displayed below fields on mobile (and in the right drawer); the other
is displayed on a separate tab on desktop
- In read mode, hide tabs if they contain no visible widgets. If there
is no tab left to display, display at least the first one with no
widgets.
- In edit mode, display all tabs and all widgets.
## Demo
https://github.com/user-attachments/assets/65ef1261-3902-4432-a420-48983b763b2c
Closes https://github.com/twentyhq/core-team-issues/issues/1811
Resolves [Dependabot Alert
301](https://github.com/twentyhq/twenty/security/dependabot/301).
Locked the version of "ai" at 5.0.52 in order to stop it from bumping to
5.0.93 directly when the ^ is introduced since it breaks the code across
multiple files.
# Introduction
Important note: for the moment testing this locally will require some
hack due to latest twenty-sdk not being published.
You will need to build twenty-cli and `cd packages/twenty-cli && yarn
link`
To finally sync the app in your app folder as `cd app-folder && twenty
app sync`
close https://github.com/twentyhq/core-team-issues/issues/1863
In this PR is introduced the generate sdk programmatic call to
[genql](https://genql.dev/) exposed in a `client` barrel of `twenty-sdk`
located in this package as there's high chances that will add a codegen
layer above it at some point ?
The cli calls this method after a sync application and writes a client
in a generated folder. It will make a graql introspection query on the
whole workspace. We should later improve that and only filter by current
applicationId and its dependencies ( when twenty-standard application is
introduced )
Fully typesafe ( input, output, filters etc ) auto-completed client
## Hello-world app serverless refactor
<img width="2480" height="1326" alt="image"
src="https://github.com/user-attachments/assets/b18ea372-b21d-4560-8fbc-1dc348427a95"
/>
---------
Co-authored-by: martmull <martmull@hotmail.fr>
Resolves [Dependabot Alert
143](https://github.com/twentyhq/twenty/security/dependabot/143).
Used `yarn up @bundled-es-modules/cookie --recursive` to move from
version `2.0.0` to `2.0.1` so that the underlying cookie dependency
version moves from `0.5.0` to `0.7.2`.
Resolves [Dependabot Alert
293](https://github.com/twentyhq/twenty/security/dependabot/293).
Updates the playwright version used to `1.56.1`. The alert could have
also been ignored since the playwright download only happens in CI and
local environments, not the production environment. However, it's an
easy fix instead of just ignoring the alert.
Resolves [Dependabot Alert
95](https://github.com/twentyhq/twenty/security/dependabot/95) - babel
vulnerable to arbitrary code execution when compiling specifically
crafted malicious code.
These were the few options we had for a direct drop-in replacement.
- [x-var](https://www.npmjs.com/package/x-var?activeTab=readme)
- [cross-let](https://www.npmjs.com/package/cross-let)
- [cross-var-no-babel](https://www.npmjs.com/package/cross-var-no-babel)
x-var has the most weekly downloads among the three and it is also the
most actively maintained fork of the original cross-var package that
introduced the vulnerability. There is no syntax difference per the
documentation, but I do not have a windows machine to test.
`cross-var-no-babel` offers the most minimal changes, but is also
abandoned without a public-facing repo.
## Introduction
Since we've moved some class validator instances from twenty-server to
twenty-shared tests are red because they do not know how to parse
decorators declarations
We've fixed this by explicitly installing `class-validator` in
`twenty-shared` and configuring jest swc accordingly
## Twenty-server class validator patch
I don't even know if that's something we need anymore
Seems to be a patch either fixing or introducing credit card and phone
number validation
Would prefer discussing the need or not to either before merging this as
it could introduce regression at runtime:
- Centralize the patch to be consumed in both `twenty-server` and
`twenty-shared`
- Remove the patch
We should also document every patch motivations we do as it's quite
though to iterate over a such huge one
Part of Fixing Issue #14976
### Pull Request Summary: SlashCommand Integration in Advanced Text
Editor
This pull request introduces **SlashCommand functionality** within the
Advanced Text Editor, specifically used in the **Workflow node** of
**Send Email body** components. The implementation leverages the
`@tiptap/suggestion` extension from the TipTap ecosystem, enabling
dynamic styling and command execution via a custom dropdown triggered by
typing `/`.
---
### Implementation Overview
#### 1. **Custom Extension & Dropdown Rendering**
- A new extension was created to handle SlashCommand interactions.
- This extension renders a **Dropdown component** that displays
available commands with relevant styles, icons etc.
#### 2. **Command Configuration**
- Each command includes:
- Visibility and active state logic
- Execution behavior upon selection
- All commands are initialized and configured centrally.
#### 3. **Search & Filtering**
- As users type after the `/`, the command list is **filtered based on
the query**.
- For example, typing `/car` filters and displays matching commands in
the dropdown.
#### 4. **Dropdown Lifecycle & Positioning**
- The dropdown is rendered using React lifecycle hooks provided by
`SuggestionTip`:
- `onStart`: Initializes state, sets selected command, and captures
cursor position via `DOMRect`.
- `onUpdate`, `onKeyDown`, `onExit`: Manage dropdown updates and
interactions.
- Positioning is handled via a **`useFloating` hook**, which aligns the
dropdown relative to the cursor and editor bounds.
- The dropdown is rendered in a **react-portal**, wrapped in the current
theme for consistent styling and animation.
#### 6. **State Management**
- A dedicated `SlashCommandState.ts` file manages:
- Callback functions
- Current command and selected item
- Cleanup utilities for event listeners
- Dropdown navigation (arrow keys, enter key)
#### 7. **Integration Points**
- SlashCommand functionality is now **enabled in Send Email body of the
Workflow**.
- Relevant changes have been applied to support this components in
Storybook as well.
[slash command
test.webm](https://github.com/user-attachments/assets/5c537844-8987-4ce5-9fca-b29ea93d8063)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Updates Portuguese (Brazil and Portugal) generated locale files,
including a new editor hint string for “Enter text or type '/' for
commands,” plus assorted translation tweaks.
>
> - **i18n/locales**:
> - **Portuguese (Brazil) `src/locales/generated/pt-BR.ts`**: Add new
editor hint message (`"Enter text or Type '/' for commands"`) and adjust
multiple translations/wording.
> - **Portuguese (Portugal) `src/locales/generated/pt-PT.ts`**: Add the
same editor hint message and refine numerous translations/labels.
> - No functional code changes; translation files regenerated/updated.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b3d7407525ec08da1fcfafe1309bb4fd61654dac. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Félix Malfait <felix@twenty.com>
Resolves [Dependabot Alert
224](https://github.com/twentyhq/twenty/security/dependabot/224) -
formidable relies on hexoid to prevent guessing of filenames for
untrusted executable content.
Used `yarn up formidable --recursive` to upgrade the version from 2.1.2
to 2.1.5.
# Twenty Browser Extension
A Chrome browser extension for capturing LinkedIn profiles (people and
companies) directly into Twenty CRM. This is a basic **v0** focused
mostly on establishing a architectural foundation.
## Overview
This extension integrates with LinkedIn to extract profile information
and create records in Twenty CRM. It uses **WXT** as the framework -
initially tried Plasmo, but found WXT to be significantly better due to
its extensibility and closer alignment with the Chrome extension APIs,
providing more control and flexibility.
## Architecture
### Package Structure
The extension consists of two main packages:
1. **`twenty-browser-extension`** - The main extension package (WXT +
React)
2. **`twenty-apps/browser-extension`** - Serverless functions for API
interactions
### Extension Components
#### Entrypoints
- **Background Script** (`src/entrypoints/background/index.ts`)
- Handles extension messaging protocol
- Manages API calls to serverless functions
- Coordinates communication between content scripts and popup
- **Content Scripts**
- **`add-person.content`** - Injects UI button on LinkedIn person
profiles
- **`add-company.content`** - Injects UI button on LinkedIn company
profiles
- Both scripts use WXT's `createIntegratedUi` for seamless DOM injection
- Extract profile data from LinkedIn DOM
- **Popup** (`src/entrypoints/popup/`)
- React-based popup UI
- Displays extracted profile information
- Provides buttons to save person/company to Twenty
#### Messaging System
Uses `@webext-core/messaging` for type-safe communication between
extension components:
```typescript
// Defined in src/utils/messaging.ts
- getPersonviaRelay() - Relays extraction from content script
- getCompanyviaRelay() - Relays extraction from content script
- extractPerson() - Extracts person data from LinkedIn DOM
- extractCompany() - Extracts company data from LinkedIn DOM
- createPerson() - Creates person record via serverless function
- createCompany() - Creates company record via serverless function
- openPopup() - Opens extension popup
```
#### Serverless Functions
Located in
`packages/twenty-apps/browser-extension/serverlessFunctions/`:
- **`/s/create/person`** - Creates a new person record in Twenty
- **`/s/create/company`** - Creates a new company record in Twenty
- **`/s/get/person`** - Retrieves existing person record (placeholder)
- **`/s/get/company`** - Retrieves existing company record (placeholder)
## Development Guide
### Prerequisites
- Twenty CLI installed globally: `npm install -g twenty-cli`
- API key from Twenty: https://twenty.com/settings/api-webhooks
### Setup
```
1. **Configure environment variables:**
- Set `TWENTY_API_URL` in the serverless function configuration
- Set `TWENTY_API_KEY` (marked as secret) in the serverless function
configuration
- For local development, create a `.env` file or configure via
`wxt.config.ts`
### Development Commands
```bash
# Start development server with hot reload
npx nx run dev twenty-browser-extension
# Build for production
npx nx run build twenty-browser-extension
# Package extension for distribution
npx nx run package twenty-browser-extension
```
### Development Workflow
1. **Start the dev server:**
```bash
npx nx run dev twenty-browser-extension
```
This starts WXT in development mode with hot module reloading.
2. **Load extension in Chrome:**
- Navigate to `chrome://extensions/`
- Enable "Developer mode"
- Click "Load unpacked"
- Select `packages/twenty-browser-extension/dist/chrome-mv3-dev/`
3. **Test on LinkedIn:**
- Navigate to a LinkedIn person profile:
`https://www.linkedin.com/in/...`
- Navigate to a LinkedIn company profile:
`https://www.linkedin.com/company/...`
- The "Add to Twenty" button should appear in the profile header
- Click the button to open the popup and save to Twenty
### Project Structure
```
packages/twenty-browser-extension/
├── src/
│ ├── common/
│ │ └── constants/ # LinkedIn URL patterns
│ ├── entrypoints/
│ │ ├── background/ # Background service worker
│ │ ├── popup/ # Extension popup UI
│ │ ├── add-person.content/ # Content script for person profiles
│ │ └── add-company.content/ # Content script for company profiles
│ ├── ui/ # Shared UI components and theme
│ └── utils/ # Messaging utilities
├── public/ # Static assets (icons)
├── wxt.config.ts # WXT configuration
└── project.json # Nx project configuration
```
## Current Status (v0)
This is a foundational version focused on architecture. Current
features:
✅ Inject UI buttons into LinkedIn profiles
✅ Extract person and company data from LinkedIn
✅ Display extracted data in popup
✅ Create person records in Twenty
✅ Create company records in Twenty
## Planned Features
- [ ] Provide a way to have API key and custom remote URLs.
- [ ] Detect if record already exists and prevent duplicates
- [ ] Open existing Twenty record when clicked (instead of creating
duplicate)
- [ ] Sidepanel Overlay UI for rich profile viewing/editing
- [ ] Enhanced data extraction (email, phone, etc.)
- [ ] Better error handling
# Demo
https://github.com/user-attachments/assets/0bbed724-a429-4af0-a0f1-fdad6997685ehttps://github.com/user-attachments/assets/85d2301d-19ee-43ba-b7f9-13ed3915f676
@charlesBochet had a conversation with Felix and he said we don't need
to spend time upgrading `zapier-platform-core` and `zapier-platform-cli`
since `twenty-zapier` will be deprecated anyway, making those packages
irrelevant.
I have updated tmp to a safer version elsewhere using `yarn up tmp
--recursive`. Also added `yarn.lock` to both server and front ci.
The massive changes in `yarn.lock` were introduced by
`zapier-platform-cli` version 17x - not sure if they caused those
breaking changes, but if they did and we still want update
zapier-related packages, I will take that up in another PR.
Resolves [Dependabot Alert
255](https://github.com/twentyhq/twenty/security/dependabot/255) - fix:
tmp allows arbitrary temporary file / directory write via symbolic link
`dir` parameter.
Updated the dev-dependency `zapier-platform-cli` for it to depend on tmp
0.2.4 and also ran `yarn up tmp --recursive` to update the version of
tmp elsewhere.
Not expecting any breaking changes to twenty-zapier since
`zapier-platform-cli` is marked as a development dependency.
Co-authored-by: martmull <martmull@hotmail.fr>
This PR implements the necessary tools to have `react-datepicker`
calendar and our date picker components work reliably no matter the
timezone difference between the user execution environment and the user
application timezone.
Fixes https://github.com/twentyhq/core-team-issues/issues/1781
This PR won't cover everything needed to have Twenty handle timezone
properly, here is the follow-up issue :
https://github.com/twentyhq/core-team-issues/issues/1807
# Features in this PR
This PR brings a lot of features that have to be merged together.
- DATE field type is now handled as string only, because it shouldn't
involve timezone nor the JS Date object at all, since it is a day like a
birthday date, and not an absolute point in time.
- DATE_TIME field wasn't properly handled when the user settings
timezone was different from the system one
- A timezone abbreviation suffix has been added to most DATE_TIME
display component, only when the timezone is different from the system
one in the settings.
- A lot of bugs, small features and improvements have been made here :
https://github.com/twentyhq/core-team-issues/issues/1781
# Handling of timezones
## Essential concepts
This topic is so complex and easy to misunderstand that it is necessary
to define the precise terms and concepts first. It resembles character
encoding and should be treated with the same care.
- Wall-clock time : the time expressed in the timezone of a user, it is
distinct from the absolute point in time it points to, much like a
pointer being a different value than the value that it points to.
- Absolute time : a point in time, regardless of the timezone, it is an
objective point in time, of course it has to be expressed in a given
timezone, because we have to talk about when it is located in time
between humans, but it is in fact distinct from any wall clock time, it
exists in itself without any clock running on earth. However, by
convention the low-level way to store an absolute point in time is in
UTC, which is a timezone, because there is no way to store an absolute
point in time without a referential, much like a point in space cannot
be stored without a referential.
- DST : Daylight Save Time, makes the timezone shift in a specific
period every year in a given timezone, to make better use of longer days
for various reasons, not all timezones have DST. DST can be 1 hour or 30
min, 45 min, which makes computation difficult.
- UTC : It is NOT an “absolute timezone”, it is the wall-clock time at
0° longitude without DST, which is an arbitrary and shared human
convention. UTC is often used as the standard reference wall-clock time
for talking about absolute point in time without having to do timezone
and DST arithmetic. PostgreSQL stores everything in UTC by convention,
but outputs everything in the server’s SESSION TIMEZONE.
## How should an absolute point in time be stored ?
Since an absolute point in time is essentially distinct from its
timezone it could be stored in an absolute way, but in practice it is
impossible to store an absolute point in time without a referential. We
have to say that a rocket launched at X given time, in UTC, EST, CET,
etc. And of course, someone in China will say that it launched at 10:30,
while in San Francisco it will have launched at 19:30, but it is THE
SAME absolute point in time.
Let’s take a related example in computer science with character
encoding. If a text is stored without the associated encoding table, the
correct meaning associated to the bits stored in memory can be lost
forever. It can become impossible for a program to guess what encoding
table should be used for a given text stored as bits, thus the glitches
that appeared a lot back in the early days of internet and document
processing.
The same can happen with date time storing, if we don’t have the
timezone associated with the absolute point in time, the information of
when it absolutely happened is lost.
It is NOT necessary to store an absolute point in time in UTC, it is
more of a standard and practical wall-clock time to be associated with
an absolute point in time. But an absolute point in time MUST be store
with a timezone, with its time referential, otherwise the information of
when it absolutely happened is lost.
For example, it is easier to pass around a date as a string in UTC, like
`2024-01-02T00:00:00Z` because it allows front-end and back-end code to
“talk” in the same standard and DST-free wall-clock time, BUT it is not
necessary. Because we have date libraries that operate on the standard
ISO timezone tables, we can talk in different timezone and let the
libraries handle the conversion internally.
It is false to say that UTC is an absolute timezone or an absolute point
in time, it is just the standard, conventional time referential, because
one can perfectly store every absolute points in time in UTC+10 with a
complex DST table and have the exactly correct absolute points in time,
without any loss of information, without having any UTC+0 dates
involved.
Thus storing an absolute point in time without a timezone associated,
for example with `timestamp` PostgreSQL data type, is equivalent to
storing a wall-clock time and then throwing away voluntarily the
information that allows to know when it absolutely happened, which is a
voluntary data-loss if the code that stores and retrieves those
wall-clock points in time don’t store the associated timezone somewhere.
This is why we use `timestamptz` type in PostgreSQL, so that we make
sure that the correct absolute point in time is stored at the exact time
we send it to PostgreSQL server, no matter the front-end, back-end and
SQL server's timezone differences.
## The JavaScript Date object
The native JavaScript Date object is now officially considered legacy
([source](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)),
the Date object stores an absolute point in time BUT it forces the
storage to use its execution environment timezone, and one CANNOT modify
this timezone, this is a legacy behavior.
To obtain the desired result and store an absolute point in time with an
arbitrary timezone there are several options :
- The new Temporal API that is the successor of the legacy Date object.
- Moment / Luxon / @date-fns/tz that expose objects that allow to use
any timezone to store an absolute point in time.
## How PostgreSQL stores absolute point in times
PostgreSQL stores absolute points in time internally in UTC
([source](https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-INPUT-TIME-STAMPS)),
but the output date is expressed in the server’s session timezone
([source](https://www.postgresql.org/docs/current/sql-set.html)) which
can be different from UTC.
Example with the object companies in Twenty seed database, on a local
instance, with a new “datetime” custom column :
<img width="374" height="554" alt="image"
src="https://github.com/user-attachments/assets/4394cb43-d97e-4479-801d-ca068f800e39"
/>
<img width="516" height="524" alt="image"
src="https://github.com/user-attachments/assets/b652f36a-d2e2-47a4-8950-647ca688cbbd"
/>
## Why can’t I just use the JavaScript native Date object with some
manual logic ?
Because the JavaScript Date object does not allow to change its internal
timezone, the libraries that are based on it will behave on the
execution environment timezone, thus leading to bugs that appear only on
the computers of users in a timezone but not for other in another
timezone.
In our case the `react-datepicker` library forces to use the `Date`
object, thus forcing the calendar to behave in the execution environment
system timezone, which causes a lot of problems when we decide to
display the Twenty application DATE_TIME values in another timezone than
the user system one, the bugs that appear will be of the off-by-one date
class, for example clicking on 23 will select 24, thus creating an
unreliable feature for some system / application timezone combinations.
A solution could be to manually compute the difference of minutes
between the application user and the system timezones, but that’s not
reliable because of DST which makes this computation unreliable when DST
are applied at different period of the year for the two timezones.
## Why can’t I compute the timezone difference manually ?
Because of DST, the work to compute the timezone difference reliably,
not just for the usual happy path, is equivalent to developing the
internal mechanism of a date timezone library, which is equivalent to
use a library that handles timezones.
## Using `@date-fns/tz` to solve this problem
We could have used `luxon` but it has a heavy bundle size, so instead we
rely here on `@date-fns/tz` (~1kB) which gives us a `TZDate` object that
allows to use any given timezone to store an absolute point-in-time.
The solution here is to trick `react-datepicker` by shifting a Date
object by the difference of timezone between the user application
timezone and the system timezone.
Let’s take a concerte example.
System timezone : Midway, ⇒ UTC-11:00, has no DST.
User application timezone : Auckland, NZ ⇒ UTC+13:00, has a DST.
We’ll take the NZ daylight time, so that will make a timezone difference
of 24 hours !
Let’s take an error-prone date : `2025-01-01T00:00:00` . This date is
usually a good test-case because it can generate three classes of bugs :
off-by-one day bugs, off-by-one month bugs and off-by-one year bugs, at
the same time.
Here is the absolute point in time we take expressed in the different
wall-clock time points we manipulate
Case | In system timezone ⇒ UTC-11 | In UTC | In user application
timezone ⇒ UTC+13
-- | -- | -- | --
Original date | `2024-12-31T00:00:00-11:00` | `2024-12-31T11:00:00Z` |
`2025-01-01T00:00:00+13:00`
Date shifted for react-datepicker | `2025-01-01T00:00:00-11:00` |
`2025-01-01T11:00:00Z` | `2025-01-02T00:00:00+13:00`
We can see with this table that we have the number part of the date that
is the same (`2025-01-01T00:00:00`) but with a different timezone to
“trick” `react-datepicker` and have it display the correct day in its
calendar.
You can find the code in the hooks
`useTurnPointInTimeIntoReactDatePickerShiftedDate` and
`useTurnReactDatePickerShiftedDateBackIntoPointInTime` that contain the
logic that produces the above table internally.
## Miscellaneous
Removed FormDateFieldInput and FormDateTimeFieldInput stories as they do
not behave the same depending of the execution environment and it would
be easier to put them back after having refactored FormDateFieldInput
and FormDateTimeFieldInput
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Resolves [Dependabot Alert
299](https://github.com/twentyhq/twenty/security/dependabot/299) -
validator has a URL validation bypass vulnerability in its isURL
function.
Used `yarn up validator --recursive` to update the version in yarn.lock
file.