Componentes e formulários
Você manda dados tipados; o Trivo desenha cada pixel. É o que impede um botão de terceiro de se parecer com um diálogo do sistema.
Onde eles são escritos
await interaction.reply("Escolha uma cor:", {
components: [
{ type: "row", items: [
{ type: "button", id: "azul", label: "Azul", style: "primary" },
{ type: "button", id: "verde", label: "Verde" },
]},
{ type: "card", title: "Tempo agora", description: "Vinte e um graus.", color: "accent" },
],
});O mesmo vale para deferredReply e followUp. Para pedir texto, o gesto é interaction.showModal(…), e o envio chega como uma interação nova, do tipo form, pelo mesmo onInteraction.
A régua, antes de qualquer campo
Nada de HTML, nada de CSS, nada de estilo embutido, nada de URL de imagem arbitrária. Um App não pinta a interface do Trivo — e a razão não é estética: um botão que pareça um aviso do sistema é phishing renderizado pelo nosso próprio cliente.
O que o parser não conhece é recusado na publicação, e não degradado em silêncio. Diferente do texto de mensagem, aqui não existe histórico a preservar: o primeiro componente já nasce validado.
Onde eles entram
components vai no corpo de qualquer uma das três portas de resposta — a confirmação, a resposta diferida e o seguimento — e também no corpo com que você responde ao POST assinado.
{
"type": "reply",
"content": "Escolha uma cor:",
"components": [
{ "type": "row", "items": [
{ "type": "button", "id": "azul", "label": "Azul", "style": "primary" },
{ "type": "button", "id": "verde", "label": "Verde" }
]},
{ "type": "card", "title": "Tempo agora", "description": "Vinte e um graus.", "color": "accent" }
]
}Os blocos
| Bloco | O que é | Cabe junto? |
|---|---|---|
row | Uma linha de até 5 itens: botão, link ou seleção. | Sim, lado a lado. |
card | Título, texto, cor, linhas de dado e rodapé. | Ocupa a largura inteira. |
No máximo 5 blocos por mensagem, e o conjunto inteiro tem teto de 8192 bytes — a conta é em bytes, não em caracteres, porque é isso que o banco e o fio carregam.
Os itens de uma linha
| Item | Campos | O que volta quando alguém aciona |
|---|---|---|
button | id, label, style (default | primary | danger), disabled | componentId, sem valores |
link | label, url (só https:) | Nada: link não aciona interação, e por isso não tem id |
select | id, placeholder, options (até 25), min, max | componentId e values |
A forma de uma opção de select
{ "type": "select", "id": "cor", "placeholder": "Escolha uma cor",
"options": [
{ "value": "azul", "label": "Azul", "description": "o padrão" },
{ "value": "verde", "label": "Verde" }
] }- Cada opção é
{ value, label, description? }— e repare que a sugestão do autocompletar, que se parece com isto, usanameno lugar delabel. São dois vocabulários vizinhos e diferentes: o de Interações existe para entrar na linha de texto de quem digita, e este existe para virar uma lista desenhada pelo Trivo. - Um
selectocupa a linha inteira: ele abre uma lista por cima do vizinho, e dividir a linha produziria alvo de toque espremido no celular. - O
idé único na mensagem toda, e não por linha: o acionamento volta com o id e mais nada, então dois botões com o mesmo id são a mesma interação para você. - O id é roteamento, nunca autorização. Ele voltou do cliente e é tão confiável quanto qualquer coisa que voltou do cliente. Quem prova de quem o componente é são a mensagem e a linha dela, do nosso lado — mas o que o botão FAZ é decisão sua, e a mensagem com botões é pública: qualquer um a vê.
O cartão
{
"type": "card",
"title": "Chamado #418",
"description": "Aberto por **Ana** há dois dias.",
"color": "danger",
"fields": [
{ "name": "Estado", "value": "Em análise", "inline": true },
{ "name": "Prioridade", "value": "Alta", "inline": true }
],
"footer": "Atualizado agora"
}- A cor sai de um conjunto fechado — neutral, accent, success, danger —, e nunca de um hexadecimal. Cada nome vira um token do nosso design system e acompanha o tema de quem lê; um valor fixo sumiria em metade dos temas.
- O texto usa a gramática do Trivo, a mesma do resto do produto: negrito, link
https:e lista. Não há um segundo markdown aqui. inlineé pedido, não ordem: quem decide se cabe lado a lado é a largura de quem lê. Num celular estreito os campos empilham.- Um cartão precisa de
title,descriptionoufields. Sem os três ele é um retângulo colorido, e é recusado. - Imagem ainda não existe. Ela será por chave de anexo, nunca por URL externa — senão o cliente de quem lê vira rastreador de terceiro —, e a chave depende de um caminho de upload que os Apps ainda não têm.
O formulário
Responda com type: "modal" para abrir um formulário na tela de quem acionou. A interação fecha em answered — você respondeu, e respondeu com um formulário —, e o envio é uma interação nova, do tipo form, que chega pelo caminho de sempre.
{
"type": "modal",
"modal": {
"id": "sugestao",
"title": "Mandar sugestão",
"fields": [
{ "type": "text", "id": "titulo", "label": "Título", "required": true, "maxLength": 60 },
{ "type": "paragraph", "id": "corpo", "label": "Detalhes", "placeholder": "O que você faria?" }
]
}
}| Campo | Vale para | Observação |
|---|---|---|
type | text | paragraph | Uma linha ou um parágrafo. São os dois. |
required | Ambos | Conferido de novo aqui, contra a definição que guardamos. |
minLength | Ambos | Só se o campo não veio vazio. |
maxLength | Ambos | Até 4000 caracteres. |
- Até 5 campos, e pelo menos um.
- Guardamos a definição. O que chega no envio é conferido contra ela — um campo que você não declarou é recusado, e os valores voltam para você na ordem em que você os declarou, não na que o cliente mandou.
- Um formulário, um envio. O segundo leva
FORM_NOT_FOUND, que é a mesma resposta para "já foi enviado", "venceu", "não existe" e "é de outra pessoa". modalnão vale sobreautocompletenem sobreform: um formulário que abre outro formulário é laço sem saída.
O que chega quando alguém envia
{
"type": "form",
"data": {
"modalId": "sugestao",
"fields": [
{ "id": "titulo", "value": "Botão de exportar" },
{ "id": "corpo", "value": "" }
]
}
}Campo que ninguém preencheu chega como texto vazio, e não ausente: duas formas de dizer "em branco" obrigariam você a lidar com as duas.