Files
twenty/packages
Thomas Trompette 11447bd96f fix: make add-select-option work on record detail pages (#23420)
Fixes #23339

Follow-up to #23410, which fixed the neighbouring issue (#23341) for
users *without* the `DATA_MODEL` permission.

## Problem

In #23339 the reporter added a multiselect field to Person and then
found the inline "create option" prompt unresponsive. They clearly have
`DATA_MODEL` permission, since they just created the field, so the
permission check isn't what's blocking them.

The blocker is the *other* precondition. Both hooks read the object name
from the router:

```ts
const { objectNamePlural } = useParams();
```

`objectNamePlural` only exists on record index routes
(`/objects/:objectNamePlural`). Record **detail** pages are
`/object/:objectNameSingular/:objectRecordId`, so on a record page the
param is `undefined` and:

- `useCanAddSelectOption` returns false via
`isNonEmptyString(objectNamePlural)`
- `useAddSelectOption` bails out at `if (!fieldName ||
!objectNamePlural) return;`

So the action was dead on record pages for **every** user, admins
included, and the navigation target it needed was never reachable from
there.

## Fix

Resolve the field and its object from `fieldMetadataId`, which
`FieldDefinition` already carries, instead of reading the object name
off the URL:

```ts
const { fieldMetadataItem, objectMetadataItem } =
  useFieldMetadataItemById(fieldMetadataId);
```

This drops the route dependency entirely, so the action behaves the same
wherever the field is rendered, and `canAddSelectOption` now reflects
only the real permission check.

It also lets both hooks take a single `fieldMetadataId` argument:
`fieldMetadataItem.name` is the same value the callers were previously
passing as `fieldName`, so that parameter is no longer needed. Resolving
both values from one id means the guard and the action can't disagree
about which field they're describing.

`useFieldMetadataItemById` is used rather than
`useFieldMetadataItemByIdOrThrow` because a lookup miss should disable
the prompt, not crash the field input.

## Reproduction

On the code the reporter was running (immediately before #23410), as an
**admin** with full `DATA_MODEL`, on a company record page, typing a
value matching no option:

- `Add "…" to options` renders
- clicking it does nothing — URL unchanged, no navigation
- pressing <kbd>Enter</kbd> does nothing either

which matches #23339 exactly, including the note about the Enter
keypress.

After #23410 the same root cause shows up differently: the prompt is no
longer rendered at all on record pages, since the guard it's now gated
on is false there. Still broken, just silent.

## Testing

Verified manually on a local instance, swapping only these files between
three states and re-running the identical steps on the same cell.

| code state | user | route | result |
|---|---|---|---|
| before #23410 | Admin | `/object/company/:id` | prompt shown, click
and Enter both do nothing |
| current main | Admin | `/object/company/:id` | prompt not shown,
action unreachable |
| **this PR** | Admin | `/object/company/:id` | prompt shown, navigates
to `/settings/objects/companies/workPolicy?newOption=…` |
| **this PR** | Admin | `/objects/tasks` (`Status`, single select) |
still works, navigates to `/settings/objects/tasks/status?newOption=…` |
| **this PR** | Member (no `DATA_MODEL`) | `/object/company/:id` |
prompt not shown |

The settings form opens with the typed value prefilled alongside the
existing options, so the end-to-end flow works from a record page for
the first time.

The Member row confirms #23341 stays fixed: dropping the route
dependency doesn't weaken the permission gate. The single-select row
covers `SelectFieldInput`, which takes the same change.

`nx lint:diff-with-main twenty-front` and `nx typecheck twenty-front`
both pass.
2026-07-28 12:27:26 +00:00
..