snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
---
title: Estendendo objetos
description: Adicione campos a objetos padrão do Twenty (Person, Company, …) ou a objetos de outros apps usando defineField.
icon: wand-magic-sparkles
---
Use `defineField()` para adicionar um campo a um objeto que você não possui — um objeto padrão do Twenty como Person ou Company, ou um objeto disponibilizado por outro app instalado. Ao contrário dos campos inline declarados dentro de [`defineObject`](/l/pt/developers/extend/apps/data/objects), os campos independentes exigem um `objectUniversalIdentifier` para especificar qual objeto eles estendem.
```ts src/fields/company-loyalty-tier.field.ts
import { defineField, FieldType } from 'twenty-sdk/define';
export default defineField({
universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890',
objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object
name: 'loyaltyTier',
type: FieldType.SELECT,
label: 'Loyalty Tier',
icon: 'IconStar',
options: [
{ value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' },
{ value: 'SILVER', label: 'Silver', position: 1, color: 'gray' },
{ value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' },
],
});
```
## Pontos-chave
* `objectUniversalIdentifier` identifica o objeto de destino. Para objetos padrão do Twenty, importe a constante de `twenty-sdk`:
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier
// …
```
* Ao definir campos **inline dentro de `defineObject()`**, você **não** precisa de `objectUniversalIdentifier` — ele é herdado do objeto pai.
* `defineField()` é a única forma de adicionar campos a objetos que você não criou com `defineObject()`.
* A localização do arquivo fica a seu critério. A convenção é `src/fields/\<name>.field.ts`, mas o SDK detecta campos em qualquer lugar dentro de `src/`.
* A `writability` também pode ser definida por campo: `MetadataWritability.APPLICATION` restringe as gravações às funções de lógica do seu próprio aplicativo, e `MetadataWritability.SYSTEM` é reservado para dados gerenciados pela plataforma. A capacidade de gravação de um campo só pode ser mais restrita do que o nível do seu objeto, e as leituras não são afetadas. Consulte [Objects](/l/pt/developers/extend/apps/data/objects) para a semântica completa.
* Para adicionar uma aba a um layout de página padrão (por exemplo, a página de detalhes de Task ou Company), use [`definePageLayoutTab`](/l/pt/developers/extend/apps/layout/page-layouts#definepagelayouttab) com `STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS` de `twenty-sdk/define`.
## Adicionando uma relação a um objeto existente
Para adicionar um campo de relação (por exemplo, vinculando seu objeto personalizado a um `Person` padrão), use `defineField()` com `FieldType.RELATION`. O padrão é o mesmo que para relações inline, mas com `objectUniversalIdentifier` definido explicitamente. Veja [Relações](/l/pt/developers/extend/apps/data/relations) para o padrão bidirecional.

View File

@@ -0,0 +1,143 @@
---
title: Objetos
description: Declare novos tipos de registro — tabelas personalizadas com seus próprios campos — usando defineObject.
icon: table
---
**Objetos** personalizados são novos tipos de registro que o seu app adiciona a um espaço de trabalho — cartão‑postal, fatura, assinatura, qualquer coisa específica do seu domínio. Cada objeto declara seu esquema (campos, relações, valores padrão) e um identificador universal estável que persiste entre sincronizações e implantações.
```ts src/objects/post-card.object.ts
import { defineObject, FieldType } from 'twenty-sdk/define';
enum PostCardStatus {
DRAFT = 'DRAFT',
SENT = 'SENT',
DELIVERED = 'DELIVERED',
RETURNED = 'RETURNED',
}
export default defineObject({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post Card',
labelPlural: 'Post Cards',
description: 'A post card object',
icon: 'IconMail',
fields: [
{
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
name: 'content',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
name: 'recipientName',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
name: 'recipientAddress',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
},
{
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
name: 'status',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
],
},
{
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
name: 'deliveredAt',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
},
],
});
```
## Pontos-chave
* O `universalIdentifier` deve ser exclusivo e estável entre implantações.
* Cada campo requer `name`, `type`, `label` e seu próprio `universalIdentifier` estável.
* O array `fields` é opcional — você pode definir objetos sem campos personalizados.
* `openRecordIn` define onde os registros deste objeto são abertos quando clicados: `ObjectOpenRecordIn.USER_CHOICE` (o padrão, seguindo a preferência de cada membro do workspace em Settings → Experience), `ObjectOpenRecordIn.SIDE_PANEL` ou `ObjectOpenRecordIn.RECORD_PAGE`. Fixe em `RECORD_PAGE` para registros que precisam de uma página inteira para serem utilizáveis, como acontece com fluxos de trabalho e dashboards, ou em `SIDE_PANEL` para registros que só fazem sentido como um painel rápido, como acontece com eventos de calendário.
* `writability` controla quem pode escrever registros do objeto em geral, antes que as permissões de papéis sejam aplicadas: `MetadataWritability.OPEN` (o padrão — os papéis do workspace determinam), `MetadataWritability.APPLICATION` (apenas as próprias funções de lógica do seu app podem criar, atualizar ou excluir registros; use isto para objetos de configuração cujos registros determinam o comportamento, de modo que membros do workspace com amplo acesso a registros não possam editá-los por meio da API) ou `MetadataWritability.SYSTEM` (reservado para dados gerenciados pela plataforma). Leituras não são afetadas — isto é aplicado no lado do servidor, diferentemente de `isUIEditable`, que apenas oculta elementos da interface. Ele também existe por campo, onde só pode ser mais restritivo do que o nível do objeto.
* Campos inline definidos aqui **não** precisam de `objectUniversalIdentifier` — ele é herdado do objeto pai. Use [`defineField()`](/l/pt/developers/extend/apps/data/extending-objects) para adicionar campos a objetos que não pertencem a você.
* Você pode criar novos objetos com `yarn twenty dev:add object`, que orienta você na definição de nomes, campos e relacionamentos. Veja [Arquitetura → Scaffolding de entidades](/l/pt/developers/extend/apps/getting-started/scaffolding).
<Note>
**Os campos base são adicionados automaticamente.** Quando você define um objeto personalizado, o Twenty cria campos padrão como `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt` para você. Você não precisa declará‑los no seu array `fields` — apenas seus campos personalizados. Você pode substituir um campo padrão declarando um com o mesmo nome, mas isso raramente é uma boa ideia.
</Note>
## Tipos de campo
O conjunto completo de valores de `FieldType`, exportados de `twenty-sdk/define`:
| Categoria | Tipos |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Texto | `TEXT`, `RICH_TEXT`, `ARRAY` (de strings), `RAW_JSON` |
| Numérico | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (precisão arbitrária), `RATING`, `POSITION` |
| Datas | `DATE`, `DATE_TIME` |
| Escolha | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Composto | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Identificadores e relações | `UUID`, `RELATION`, `MORPH_RELATION` (veja [Relações](/l/pt/developers/extend/apps/data/relations)) |
| Sistema | `TS_VECTOR` (vetor de pesquisa de texto completo, gerenciado pelo servidor) |
Tipos compostos armazenam vários subcampos (por exemplo, `FULL_NAME` = primeiro + último nome; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` e `MULTI_SELECT` exigem um array `options`, como no exemplo acima.
## Valores padrão
Valores padrão de strings literais devem ser colocados entre aspas simples **dentro** da string — `defaultValue: "'Draft'"`, não `defaultValue: "Draft"`. É por isso que o campo `status` acima usa `` `'${PostCardStatus.DRAFT}'` ``.
Strings sem aspas são reservadas para valores padrão computados, avaliados quando um registro é criado:
* `'uuid'` — gera um UUID (para campos `UUID`)
* `'now'` — o carimbo de data/hora atual (para campos `DATE_TIME`)
A mesma convenção se aplica a subcampos de string de valores padrão compostos (por exemplo, `{ source: "'MANUAL'" }` em um campo `ACTOR`) e a valores de `SELECT`/`MULTI_SELECT`. Uma string literal padrão deixada sem aspas gera um aviso quando seu app é compilado.
## Nulabilidade
`isNullable` controla se um campo aceita `NULL`. O valor padrão é `true` — omita-o para campos opcionais. Defina `isNullable: false` para tornar um campo obrigatório no nível do banco de dados.
Alterações em `isNullable` são aplicadas em todas as sincronizações, incluindo sincronizações que atualizam um campo existente — assim, você pode alternar a nulabilidade de um campo editando o manifesto e sincronizando novamente.
<Note>
**Tornar um campo existente obrigatório requer um valor padrão.** Quando você altera um campo para `isNullable: false`, também deve fornecer um `defaultValue` não nulo. O valor padrão preenche retroativamente quaisquer linhas `NULL` existentes antes de a restrição `NOT NULL` ser aplicada; sem ele a sincronização falha com `Default value cannot be null for non-nullable fields`. Campos de relação e campos `TS_VECTOR` sempre aceitam nulos, portanto `isNullable` não tem efeito sobre eles.
</Note>
```ts
{
universalIdentifier: 'b1a7c0de-1234-4f00-9abc-000000000000',
name: 'reference',
type: FieldType.TEXT,
label: 'Reference',
isNullable: false,
defaultValue: "'N/A'",
}
```
## O que vem depois
* **Conecte este objeto a outros** — veja [Relações](/l/pt/developers/extend/apps/data/relations) para o padrão de relação bidirecional.
* **Adicione campos a objetos de outros apps** — veja [Extensão de objetos](/l/pt/developers/extend/apps/data/extending-objects) para `defineField()`.
* **Exibir este objeto na interface do usuário (UI)** — consulte [Itens do menu de navegação](/l/pt/developers/extend/apps/layout/navigation-menu-items) para adicionar uma entrada na barra lateral; consulte [Views](/l/pt/developers/extend/apps/layout/views) para adicionar configurações de lista personalizadas.

View File

@@ -0,0 +1,101 @@
---
title: Visão Geral
description: Modele os dados que seu app adiciona a um workspace — objetos, campos e relações.
icon: database
---
A **camada de dados** de um app Twenty é o conjunto de dados que seu app *adiciona* a um workspace — os novos tipos de registros que ele declara, as colunas que adiciona a objetos existentes e como esses registros se conectam entre si.
```text
┌──────────────────────────────────────────────────┐
│ Object — a record type, e.g. PostCard │
│ ├─ Field (name, type, label) │
│ ├─ Field │
│ └─ Relation (link to another object) │
└──────────────────────────────────────────────────┘
│
├── lives in your app, OR
│
▼
┌──────────────────────────────────────────────────┐
│ Standard / other apps' objects │
│ └─ Field added by your app via defineField │
└──────────────────────────────────────────────────┘
```
## Nesta seção
<CardGroup cols={2}>
<Card title="Objetos" icon="table" href="/l/pt/developers/extend/apps/data/objects">
`defineObject` — declare novos tipos de registros com seus próprios campos.
</Card>
<Card title="Estendendo objetos" icon="wand-magic-sparkles" href="/l/pt/developers/extend/apps/data/extending-objects">
`defineField` — adicione campos a objetos padrão ou de outros apps.
</Card>
<Card title="Relações" icon="diagram-project" href="/l/pt/developers/extend/apps/data/relations">
Conexões bidirecionais `MANY_TO_ONE` / `ONE_TO_MANY` entre objetos.
</Card>
<Card title="Tipos de atividade da cronologia" icon="clock-rotate-left" href="/l/pt/developers/extend/apps/data/timeline-activity-types">
Defina eventos de auditoria automáticos e eventos explícitos do app para linhas do tempo de registros.
</Card>
</CardGroup>
## Entidades em resumo
| Entidade | Finalidade | Definido com |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- |
| **Objeto** | Um novo tipo de registro personalizado (por exemplo, PostCard, Invoice) com seus próprios campos | `defineObject()` |
| **Campo** | Uma coluna em um objeto. Campos independentes podem estender objetos que você não criou (por exemplo, adicionar `loyaltyTier` ao objeto Company) | `defineField()` |
| **Relação** | Um vínculo bidirecional entre dois objetos — ambos os lados declarados como campos | `defineField()` com `FieldType.RELATION` |
| **Índice** | Um índice de banco de dados para acelerar uma consulta recorrente em um dos seus objetos | `defineIndex()` |
| **Tipo de atividade da cronologia** | Um evento de auditoria automático ou evento de domínio explícito exibido nas linhas do tempo de registros | `defineTimelineActivityType()` |
O SDK detecta esses elementos por meio de análise de AST em tempo de build, então a organização dos arquivos fica a seu critério — a convenção é `src/objects/`, `src/fields/`, `src/indexes/` e `src/timeline-activity-types/`. UUIDs `universalIdentifier` estáveis conectam tudo em implantações diferentes.
## Índices (Opcional)
Os apps podem incluir índices junto com seus objetos para manter rápidas as consultas recorrentes. O caso mais comum é uma coluna de status ou de chave estrangeira que você lê com frequência.
```ts src/indexes/post-card-status.index.ts
import { defineIndex } from 'twenty-sdk/define';
import {
POST_CARD_UNIVERSAL_IDENTIFIER,
STATUS_FIELD_UNIVERSAL_IDENTIFIER,
} from '../objects/post-card.object';
export default defineIndex({
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff0',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff1',
fieldUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
},
],
});
```
### Índices únicos
`defineIndex` aceita `isUnique: true` tanto para unicidade de uma única coluna quanto de múltiplas colunas. Este é o recurso recomendado — `defineField({ isUnique: true })` está obsoleto e será removido em uma versão futura.
```ts
defineIndex({
universalIdentifier: '…',
objectUniversalIdentifier: PERSON_UNIVERSAL_IDENTIFIER,
isUnique: true,
fields: [{ universalIdentifier: '…', fieldUniversalIdentifier: EMAIL_FIELD_UNIVERSAL_IDENTIFIER }],
});
```
### Outras restrições
* Cláusulas `WHERE` parciais permanecem sob controle do administrador — os apps não podem declará-las.
* Cada objeto é limitado a 10 índices personalizados (os índices do próprio framework não contam).
Ordene o array `fields` da forma como o Postgres deve usá-lo — coluna mais à esquerda primeiro, como em uma lista telefônica. Índices não são gratuitos: cada gravação na tabela os atualiza. Adicione um apenas quando você tiver uma consulta que precise dele.
<Note>
Procurando por **Application Config** ou **Roles & Permissions**? Esses descrevem o próprio app em vez dos dados que ele adiciona — eles ficam em [Config](/l/pt/developers/extend/apps/config/overview). Procurando por **Connections** (Linear, GitHub, Slack OAuth)? Essas existem para serem chamadas *a partir de* funções de lógica e ficam em [Logic](/l/pt/developers/extend/apps/logic/connections).
</Note>

View File

@@ -0,0 +1,184 @@
---
title: Relações
description: Conecte objetos entre si com relações bidirecionais MANY_TO_ONE / ONE_TO_MANY.
icon: diagram-project
---
As relações conectam dois objetos entre si. No Twenty, as relações são sempre **bidirecionais** — cada relação tem dois lados, e cada lado é declarado como um campo que faz referência ao outro.
| Tipo de relação | Descrição | Tem chave estrangeira? |
| --------------- | ----------------------------------------------------------------- | ---------------------- |
| `MANY_TO_ONE` | Muitos registros deste objeto apontam para um registro do destino | Sim (`joinColumnName`) |
| `ONE_TO_MANY` | Um registro deste objeto possui muitos registros do destino | Não (o lado inverso) |
## Como as relações funcionam
Toda relação requer **dois campos** que façam referência um ao outro:
1. O lado **MANY_TO_ONE** — fica no objeto que contém a chave estrangeira.
2. O lado **ONE_TO_MANY** — fica no objeto que possui a coleção.
Ambos os campos usam `FieldType.RELATION` e fazem referência cruzada um ao outro via `relationTargetFieldMetadataUniversalIdentifier`.
## Exemplo: Um cartão postal tem muitos destinatários
Um `PostCard` pode ser enviado para muitos registros `PostCardRecipient`. Cada destinatário pertence a exatamente um cartão postal.
**Etapa 1: Defina o lado ONE_TO_MANY em PostCard** (o lado "um"):
```ts src/fields/post-card-recipients-on-post-card.field.ts
import { defineField, FieldType, RelationType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111';
// Import from the other side
import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field';
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
icon: 'IconUsers',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
```
**Etapa 2: Defina o lado MANY_TO_ONE em PostCardRecipient** (o lado "muitos" — contém a chave estrangeira):
```ts src/fields/post-card-on-post-card-recipient.field.ts
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222';
// Import from the other side
import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field';
export default defineField({
universalIdentifier: POST_CARD_FIELD_ID,
objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
icon: 'IconMail',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
});
```
<Note>
**Importações circulares:** ambos os campos de relação fazem referência ao `universalIdentifier` um do outro. Para evitar problemas de importação circular, exporte os IDs dos seus campos como constantes nomeadas de cada arquivo e importe-os no outro. O sistema de build resolve isso em tempo de compilação.
</Note>
## Relações de junção
Um objeto de junção conecta um registro de origem a um ou mais registros de destino. Por exemplo, `PostCardRecipient` pode conter tanto uma relação `postCard` quanto uma relação `recipient`. No campo `ONE_TO_MANY` do objeto de origem, defina `junctionTargetFieldUniversalIdentifier` como o campo de relação no objeto de junção que aponta para o destino:
```ts
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
relationTargetObjectMetadataUniversalIdentifier:
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
junctionTargetFieldUniversalIdentifier: RECIPIENT_FIELD_ID,
},
});
```
`RECIPIENT_FIELD_ID` deve identificar uma relação `MANY_TO_ONE` ou morph no objeto de junção. Isso informa ao Twenty qual registro relacionado é o destino quando um recurso como [distribuição de atividade da linha do tempo](/l/pt/developers/extend/apps/data/timeline-activity-types#automatic-and-explicit-events) percorre a junção.
## Relacionando a objetos padrão
Para criar uma relação com um objeto integrado do Twenty (Person, Company, etc.), use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`:
```ts src/fields/person-on-self-hosting-user.field.ts
import {
defineField,
FieldType,
RelationType,
OnDeleteAction,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object';
export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333';
export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444';
export default defineField({
universalIdentifier: PERSON_FIELD_ID,
objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'person',
label: 'Person',
description: 'Person matching with the self hosting user',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'personId',
},
});
```
## Propriedades de campos de relação
| Propriedade | Obrigatório | Descrição |
| ---------------------------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `type` | Sim | Deve ser `FieldType.RELATION` |
| `relationTargetObjectMetadataUniversalIdentifier` | Sim | O `universalIdentifier` do objeto de destino |
| `relationTargetFieldMetadataUniversalIdentifier` | Sim | O `universalIdentifier` do campo correspondente no objeto de destino |
| `universalSettings.relationType` | Sim | `RelationType.MANY_TO_ONE` ou `RelationType.ONE_TO_MANY` |
| `universalSettings.onDelete` | Apenas para MANY_TO_ONE | O que acontece quando o registro referenciado é excluído: `CASCADE`, `SET_NULL`, `RESTRICT` ou `NO_ACTION` |
| `universalSettings.joinColumnName` | Apenas para MANY_TO_ONE | Nome da coluna no banco de dados para a chave estrangeira (por exemplo, `postCardId`) |
| `universalSettings.junctionTargetFieldUniversalIdentifier` | Somente ONE_TO_MANY de junção | Campo de relação no objeto de junção que aponta para o destino da distribuição |
## Campos de relação inline
Você também pode declarar uma relação diretamente dentro de [`defineObject`](/l/pt/developers/extend/apps/data/objects). Quando estiver inline, omita `objectUniversalIdentifier` — ele é herdado do objeto pai:
```ts
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCardRecipient',
// ...
fields: [
{
universalIdentifier: POST_CARD_FIELD_ID,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
},
// … other fields
],
});
```

View File

@@ -0,0 +1,212 @@
---
title: Referenciando metadados de sistema
description: Resolva os identificadores universais determinísticos dos metadados que o Twenty provisiona automaticamente em cada objeto, para que seu app possa referenciá-los sem hardcoding.
icon: gears
---
Todo objeto no Twenty vem com **metadados de sistema** que você nunca declara explicitamente, como um conjunto de campos e uma visualização principal de lista com suas colunas. O servidor cria tudo isso quando o objeto é provisionado, e o conjunto cresce conforme o Twenty cresce.
Como você não o declara, não existe nenhuma constante `universalIdentifier` para você importar. Em vez disso, o servidor **deriva** cada identificador de forma determinística, e o `twenty-sdk` expõe a mesma derivação para que seu manifesto possa resolver o valor exato que o servidor usa.
## Campos do sistema
Os campos escalares presentes em todo objeto, nenhum dos quais você declara com [`defineField()`](/l/pt/developers/extend/apps/data/extending-objects):
`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`
Então, como você referencia `createdAt` como uma coluna em uma [visualização](/l/pt/developers/extend/apps/layout/views)?
### O Problema
Desde o Twenty 2.19, o identificador universal de um campo de sistema é **derivado deterministicamente** pelo servidor a partir de três entradas: o identificador universal do aplicativo, o identificador universal do objeto e o nome do campo. Inventar um id e deixá-lo hardcoded não funciona: ele não corresponde a nada no servidor, e a sincronização rejeita a referência pendente:
```
Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found
```
### A Solução
<Note>
`getFieldUniversalIdentifier` está disponível a partir do `twenty-sdk` 2.21.
</Note>
Use `getFieldUniversalIdentifier` para resolver exatamente o mesmo valor que o servidor usa. Ela recebe as três entradas e retorna o identificador universal do campo:
```ts
import { getFieldUniversalIdentifier } from 'twenty-sdk/define';
const createdAtFieldId = getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
});
```
* `applicationUniversalIdentifier` é o identificador do seu app, aquele que você passa para [`defineApplication()`](/l/pt/developers/extend/apps/config/application).
* `objectUniversalIdentifier` é o identificador do objeto ao qual o campo pertence.
* `name` é o nome do campo de sistema, um dos valores listados acima.
### Exemplo: uma coluna createdAt em uma visualização
O caso típico é adicionar uma coluna `createdAt` a uma visualização de um dos seus objetos personalizados. Resolva o id do campo e referencie-o como qualquer outro `fieldMetadataUniversalIdentifier`:
```ts src/views/example-view.ts
import {
defineView,
getFieldUniversalIdentifier,
} from 'twenty-sdk/define';
const APPLICATION_UNIVERSAL_IDENTIFIER =
'0b04e15c-27b2-4741-9046-b32e07469072';
const MY_OBJECT_UNIVERSAL_IDENTIFIER =
'c782b61c-70fd-4c88-9cd6-4e61ab8d7591';
export default defineView({
universalIdentifier: '70f10d44-144a-4da8-8c6f-3ec2422138c0',
name: 'All records',
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
position: 0,
fields: [
{
universalIdentifier: '75a90bc4-d901-4df4-85e0-af29db5e0104',
fieldMetadataUniversalIdentifier: getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
}),
position: 0,
isVisible: true,
size: 200,
},
],
});
```
O mesmo id resolvido funciona em qualquer lugar onde se espera um `fieldMetadataUniversalIdentifier`: campos de visualização, filtros, ordenações, agrupamentos e widgets de layout de página.
<Note>
Resolva o id, não o deixe hardcoded. Como o servidor deriva o valor a partir do
id do aplicativo, do id do objeto e do nome do campo, chamar
`getFieldUniversalIdentifier` mantém a sua referência correta mesmo que essas
entradas mudem, e evita divergências se a derivação evoluir no futuro.
</Note>
### Campos de relação do sistema
<Note>
`getSystemRelationFieldUniversalIdentifier` está disponível a partir da versão 2.23 do `twenty-sdk` e requer um servidor Twenty na versão 2.23 ou posterior.
</Note>
Além dos campos escalares de sistema acima, o servidor também provisiona quatro **campos de relação de sistema** em cada objeto: `timelineActivities`, `attachments`, `noteTargets` e `taskTargets`, cada um apontando para o objeto de relação padrão correspondente.
Dessa forma, esses campos não são resolvidos com `getFieldUniversalIdentifier`: seu identificador é derivado de forma **independente de nome**, a partir do objeto que hospeda o campo e do objeto para o qual o campo aponta. Dessa forma, renomear um objeto nunca altera os identificadores de seus campos de relação.
Use `getSystemRelationFieldUniversalIdentifier` para resolvê-los:
```ts
import {
getSystemRelationFieldUniversalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
// rocket.attachments — the relation field hosted on your custom object
const rocketAttachmentsFieldId = getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
relationTargetObjectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
});
```
* `objectUniversalIdentifier` é o objeto que **hospeda** o campo.
* `relationTargetObjectUniversalIdentifier` é o objeto para o qual o campo **aponta**.
A direção é codificada pela ordem dos argumentos. Para resolver o lado inverso (por exemplo, `attachment.targetRocket`, o campo morph que o servidor cria no objeto de relação padrão), inverta os dois:
```ts
// attachment.targetRocket — the reverse morph field on Attachment
const attachmentTargetRocketFieldId =
getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
relationTargetObjectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
});
```
Assim como acontece com campos de sistema escalares, o id resolvido funciona em qualquer lugar em que um `fieldMetadataUniversalIdentifier` é esperado.
## Visualizações de sistema
<Note>
`getSystemViewUniversalIdentifier` e `getSystemViewFieldUniversalIdentifier`
estão disponíveis a partir da versão 2.26 do `twenty-sdk` e requerem um servidor Twenty na versão 2.26 ou posterior.
</Note>
O servidor também provisiona uma **visualização de sistema** em cada objeto: a visualização principal de lista (`All {objectLabelPlural}`, com chave `ViewKey.INDEX` (derivada com `SYSTEM_VIEW_KEYS.INDEX`)), com uma coluna por campo exibível. Assim como os campos de relação de sistema, seus identificadores são derivados **sem usar nomes**, então renomear um objeto ou um campo nunca os altera.
Use `getSystemViewUniversalIdentifier` para resolvê‑la:
```ts
import {
getSystemViewUniversalIdentifier,
SYSTEM_VIEW_KEYS,
} from 'twenty-sdk/define';
const rocketIndexViewId = getSystemViewUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
viewKey: SYSTEM_VIEW_KEYS.INDEX,
});
```
* `objectMetadataApplicationUniversalIdentifier` é o aplicativo proprietário do **objeto**, que define o namespace da visualização.
* `objectUniversalIdentifier` é o objeto que a visualização lista.
* `viewKey` é o discriminador de visualização de sistema: `SYSTEM_VIEW_KEYS.INDEX` para a visualização principal de lista, `SYSTEM_VIEW_KEYS.FIELDS_WIDGET` para a visualização do widget de campos da página de registro. Ele é a chave da derivação; apenas `INDEX` também é armazenado na linha da visualização.
O id resolvido funciona em qualquer lugar em que um `viewUniversalIdentifier` seja esperado, como uma entrada de barra lateral [`NavigationMenuItemType.VIEW`](/l/pt/developers/extend/apps/layout/navigation-menu-items). Para simplesmente abrir a lista principal de um objeto, prefira `NavigationMenuItemType.OBJECT` com `targetObjectUniversalIdentifier`: isso não precisa de derivação.
`getSystemViewFieldUniversalIdentifier` resolve uma única **coluna** em uma visualização de sistema, a partir da visualização e do campo que ela exibe:
```ts
import { getSystemViewFieldUniversalIdentifier } from 'twenty-sdk/define';
const rocketNameColumnId = getSystemViewFieldUniversalIdentifier({
fieldMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
viewUniversalIdentifier: rocketIndexViewId,
fieldMetadataUniversalIdentifier: ROCKET_NAME_FIELD_UNIVERSAL_IDENTIFIER,
});
```
Observe o primeiro argumento: uma coluna é colocada em namespace pelo aplicativo proprietário do **campo que ela exibe**, não pelo que é proprietário da visualização. Um campo que seu app adiciona a um objeto padrão tem sua coluna derivada sob o seu aplicativo, em uma visualização de propriedade do Twenty.
<Warning>
As visualizações de sistema e suas colunas são **de propriedade do servidor**: resolva seus identificadores
para referenciá‑las, nunca para declará‑las. `key` em
[`defineView()`](/l/pt/developers/extend/apps/layout/views) está obsoleto e
é ignorado, portanto uma visualização de manifesto nunca pode reivindicar a chave `INDEX`, e o servidor
já provisiona uma coluna para cada campo que você adiciona, então declarar seu próprio
`defineViewField()` para esse mesmo campo em uma visualização de sistema entra em conflito com ela.
</Warning>
## Objetos Padrão do Twenty
Para um objeto **padrão** do Twenty (Person, Company, Opportunity, …), você não precisa derivar nada: os identificadores são constantes pré‑computadas que você pode importar diretamente, tanto para campos quanto para visualizações.
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.createdAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.fields.updatedAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople.universalIdentifier
```
Use os helpers acima quando o objeto for um que **seu app** define com [`defineObject()`](/l/pt/developers/extend/apps/data/objects), em que não existe tal constante.
<Note>
`name` é um campo **padrão**, não um campo de sistema. Ele mantém seu próprio identificador universal
hardcoded e não é resolvido por meio de
`getFieldUniversalIdentifier`. Em objetos que você define, referencie o campo
`name` pelo identificador que você atribuiu a ele em `defineObject()`.
</Note>

View File

@@ -0,0 +1,184 @@
---
title: Tipos de Atividade Linha do Tempo
description: Define eventos de auditoria automáticos e eventos explícitos do aplicativo que renderizam em linhas de tempo registradas.
icon: clock-rotate-left
---
Um tipo de atividade de linha do tempo define o vocabulário estável para um evento mostrado na linha do tempo de um registro. Objetos padrão e objetos de aplicativos usam o mesmo contrato: um tipo tem um rótulo e ícone, podem opcionalmente declarar quando é emitido, e pode opcionalmente renderizar através de um dos [componentes frontais da sua aplicação](/l/pt/developers/extend/apps/layout/front-components).
<Note>
Os tipos de atividade da linha do tempo estão em beta e em breve em Vinte e 2.34. A API
pode evoluir, enquanto aprendemos com os casos de uso de desenvolvedores de aplicativos.
</Note>
Crie um com o scaffolder:
```bash filename="Terminal"
yarn twenty dev:add timelineActivityType
```
Ou defina-o diretamente:
```ts filename="src/timeline-activity-types/post-card-created.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7500',
name: 'postCardCreated',
label: 'created a post card',
icon: 'IconMail',
emit: {
on: 'created',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
},
});
```
## Eventos automáticos e explícitos
Adicione `emiss` quando Vinte deveria criar este tipo automaticamente. `emit.on` suporta `created`, `updated`, `deleted`, `restored`, `linked`, and `unlinked`, while `emit.objectUniversalIdentifier` identifica o objeto de origem. Apenas um tipo eficaz pode lidar com a mesma chave de emissão —`on`, objeto e opcional através de uma relação—em uma área de trabalho.
Sem `emit.through`, o evento está escrito na própria linha do tempo do registro de origem. Para fã para registros relacionados, defina `emit.through. elationFieldUniversalIdentifier` para uma relação muita-a-dia ou uma [relação de junção] (/developers/extend/apps/data/relations#junction-relations) direta sobre o objeto fonte. As relações morfológicas são favoráveis a todos os membros do seu grupo morfol; portanto, uma única declaração pode visar vários tipos de objeto.
Para uma relação direta, criar ou restaurar o registro fonte produz `linkado`, enquanto ele produz `unlinked`, e reapontando a relação produz `não ligado` no alvo anterior mais `ligado` no novo alvo. Outras atualizações de origem não produzem eventos de ligação. Este é o contrato usado pelos anexos, cujo alvo é uma relação de morph directo.
Para uma relação de junção, `universalSettings.junctionTargdUniversalIdentifier` identifica a relação do objeto de junção com o alvo. Criar ou excluir uma linha de junção produz `ligado` ou `unlinked`. Reapontando qualquer lado de uma linha de junção também produz um evento de link; atualizações para campos de junção não relacionam.
Eventos `ligados` e `não ligados` requerem `emit.through` porque seu gatilho é uma alteração na relação de junção ou direta configurada.
Por exemplo, este é o mesmo contrato genérico usado por notas, tarefas, mensagens e eventos do calendário:
```ts filename="src/timeline-activity-types/post-card-linked.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/post-card-recipients-on-post-card.field';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7502',
name: 'postCardLinked',
label: 'received a post card',
icon: 'IconMail',
emit: {
on: 'linked',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
through: {
relationFieldUniversalIdentifier:
POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER,
},
},
});
```
Um evento `atualizado` é emitido para cada atualização de fonte por padrão. Defina `emit.through.triggerFieldUniversalIdentifiers` quando apenas alterações para os campos de origem selecionados devem aparecer nas linhas do tempo de destino.
Omita `emit` para um evento de domínio explícito que sua função lógica cria a si mesma. Isto evita a elaboração de uma linha de auditoria automática e de uma linha explícita para a mesma operação.
```ts filename="src/timeline-activity-types/post-card-sent.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
});
```
Criar eventos explícitos com [`createTimelineActivity()`](/l/pt/developers/extend/apps/logic/logic-functions#create-a-timeline-activity). O código do aplicativo usa identificadores universais estáveis; Vinte resolvem os IDs de metadados específicos de instalação.
## Renderização personalizada
Sem um componente frontal, Vinte renderiza uma linha genérica do rótulo de tipos, ícone e metadados do objeto vinculado. Isto funciona para objetos padrão e personalizados sem um renderizador específico do objeto.
Para detalhes personalizados, defina `frontComponentUniversalIdentifier` para um componente frontal pertencente ao mesmo aplicativo:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
O cordão nativo continua a ser a apresentação desmoronada. Vinte montam o componente frontal apenas depois que o usuário se expande, evitando uma sandbox e trabalhador para cada evento visível. Dentro do componente, chame `useTimelineActivityId()` de `vinte e sdk/front-component` para ler a ID da linha e buscar quaisquer dados que sua apresentação precisar. Ele retorna `null` quando o componente é renderizado fora de uma linha do tempo.
## Sobrescrevendo a apresentação de outro aplicativo
Um aplicativo possui contratos automáticos de linha do tempo para seus próprios objetos. Para personalizar um evento em um objeto de propriedade de outro aplicativo, declare o tipo existente explicitamente com `replacesTimelineActivityTypeUniversalIdentifier`:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7503',
name: 'companyCreatedWithDeploymentContext',
label: 'was created from a deployment',
emit: {
on: 'created',
objectUniversalIdentifier: COMPANY_UNIVERSAL_IDENTIFIER,
},
replacesTimelineActivityTypeUniversalIdentifier:
RECORD_CREATED_TIMELINE_ACTIVITY_TYPE_UNIVERSAL_IDENTIFIER,
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
Uma substituição deve incluir `emiss`, porque ela substitui um slot de emissão automática existente ao invés de um tipo apenas explícito. O tipo referenciado deve pertencer à aplicação do objeto de destino e descrever a mesma ação e rota. Remover o aplicativo que substitui restaura o tipo base; remover o tipo base desativa a substituição.
## Área de trabalho substitui e silencia
Administradores da Área de Trabalho podem alterar a apresentação de um tipo ou silenciar seus eventos automáticos e explícitos sem bifurcar o aplicativo. Use a mutação do `updateTimelineActivityType` da API com o ID do tipo específico de instalação:
```graphql
mutation CustomizeTimelineActivityType(
$id: UUID!
$label: String
$icon: String
$isActive: Boolean
) {
updateTimelineActivityType(
input: { id: $id, label: $label, icon: $icon, isActive: $isActive }
) {
id
label
icon
isActive
}
}
```
'label' e 'icon' são armazenados como sobreposição de espaço de trabalho, então atualizações posteriores do aplicativo não substituem as escolhas do administrador. Coloque `isActive` como `false` para parar a emissão automática e rejeitar novos eventos explícitos desse tipo. Linhas existentes permanecem visíveis.
Restaure os padrões do aplicativo, incluindo o estado ativo, com:
```graphql
mutation ResetTimelineActivityType($id: UUID!) {
resetTimelineActivityType(id: $id) {
id
label
icon
isActive
}
}
```
## Campos de configuração
| Campo | Obrigatório | Descrição |
| ------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------ |
| `universalIdentifier` | Sim | UUID Estável para o tipo entre instalações e melhorias |
| `name` | Sim | Nome programático local do aplicativo; deve ser exclusivo dentro do aplicativo |
| `label` | Sim | Texto de ação para o usuário usado pelo renderizador nativo |
| `icon` | Não | Nome de 20 ícones exibido ao lado do evento |
| `emitir` | Não | Declaração automática de emissão; omitir para tipos explícitos |
| `emit.on` | Com emissão | Ação de auditoria que faz com que este tipo seja escrito |
| `emit.objectUniversalIdentifier` | Com emissão | Objeto cujo registros emitem este tipo |
| `emit.through.relationFieldUniversalIdentifier` | Não | Relação direta ou de cruzamento usada para ventiladores com cronogramas relacionados |
| `emit.through.triggerFieldUniversalIdentifiers` | Não | Campos de origem que podem acionar um `atualizado` através do evento |
| `frontComponentUniversalIdentifier` | Não | Componente frontal do App-owned montado quando a linha é expandida |
| `replacesTimelineActivityTypeUniversalIdentifier` | Não | Tipo de emissão existente substituído; requer `emit` |
Vinte instantâneos semânticos identidade e apresentação durável fallbacks quando um evento é criado. Linhas históricas mantêm suas ações originais e significados por objetos. Enquanto o tipo está instalado, seu rótulo traduzido, ícone e frontal componente renderizam ao vivo; depois de desinstalado, o snapshot mantém a linha legível. Resolução usa o identificador universal, e a apresentação também sobrevive à reinstalação do aplicativo.