@softize/opus 18.5.0 → 20.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +275 -0
- package/bin/cli.mjs +5 -0
- package/bin/lib/check.mjs +198 -10
- package/bin/lib/copy.mjs +29 -0
- package/bin/lib/mcp.mjs +1 -1
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +298 -0
- package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
- package/docs/data-products.md +15 -9
- package/docs/protocol.md +3 -4
- package/docs/releasing.md +2 -2
- package/package.json +2 -2
- package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
- package/registry/templates/app/package.json +1 -1
- package/src/core/runtime.ts +96 -8
- package/src/core/types.ts +5 -2
- package/src/ui/components/patterns/action-form-card.tsx +11 -1
- package/src/ui/components/patterns/content-header.tsx +16 -12
- package/src/ui/components/patterns/fill-region.ts +150 -0
- package/src/ui/components/patterns/heading-level.ts +16 -0
- package/src/ui/components/patterns/list.tsx +103 -73
- package/src/ui/components/patterns/page.tsx +20 -4
- package/src/ui/components/patterns/pane.tsx +294 -0
- package/src/ui/components/patterns/presentation.tsx +81 -54
- package/src/ui/components/patterns/sidebar.tsx +13 -72
- package/src/ui/components/patterns/split.tsx +51 -38
- package/src/ui/components/patterns/surface-anatomy.ts +246 -0
- package/src/ui/components/patterns/surface-header.tsx +51 -6
- package/src/ui/components/patterns/surface-media.tsx +69 -0
- package/src/ui/components/patterns/trigger.tsx +12 -2
- package/src/ui/components/primitives/alert.tsx +9 -1
- package/src/ui/components/primitives/avatar.tsx +7 -3
- package/src/ui/components/primitives/card.tsx +91 -29
- package/src/ui/components/primitives/carousel.tsx +34 -5
- package/src/ui/components/primitives/checkbox.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +14 -5
- package/src/ui/components/primitives/copyable.tsx +120 -10
- package/src/ui/components/primitives/detail.tsx +10 -13
- package/src/ui/components/primitives/dialog.tsx +179 -45
- package/src/ui/components/primitives/drawer.tsx +131 -24
- package/src/ui/components/primitives/empty.tsx +1 -1
- package/src/ui/components/primitives/icon-picker.tsx +6 -2
- package/src/ui/components/primitives/icon-tile.tsx +7 -0
- package/src/ui/components/primitives/input-group.tsx +9 -2
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/kbd.tsx +1 -1
- package/src/ui/components/primitives/label.tsx +3 -0
- package/src/ui/components/primitives/menu.tsx +5 -0
- package/src/ui/components/primitives/metric-card.tsx +98 -39
- package/src/ui/components/primitives/popover.tsx +1 -1
- package/src/ui/components/primitives/progress.tsx +9 -1
- package/src/ui/components/primitives/slider.tsx +62 -11
- package/src/ui/components/primitives/truncate.tsx +10 -1
- package/src/ui/docs/content/accordion.md +74 -27
- package/src/ui/docs/content/action-form-card.md +24 -15
- package/src/ui/docs/content/action-form-dialog.md +17 -7
- package/src/ui/docs/content/action-form.md +92 -77
- package/src/ui/docs/content/action-list-dialog.md +75 -38
- package/src/ui/docs/content/action-list.md +134 -52
- package/src/ui/docs/content/action-trigger.md +27 -10
- package/src/ui/docs/content/action-view.md +14 -14
- package/src/ui/docs/content/actions.md +161 -66
- package/src/ui/docs/content/ai.md +43 -44
- package/src/ui/docs/content/alert.md +83 -29
- package/src/ui/docs/content/ask.md +51 -26
- package/src/ui/docs/content/aspect-ratio.md +22 -24
- package/src/ui/docs/content/audit.md +23 -19
- package/src/ui/docs/content/auth.md +38 -19
- package/src/ui/docs/content/avatar.md +54 -5
- package/src/ui/docs/content/badge.md +29 -9
- package/src/ui/docs/content/breadcrumb.md +21 -8
- package/src/ui/docs/content/button.md +73 -43
- package/src/ui/docs/content/calendar.md +134 -26
- package/src/ui/docs/content/card.md +252 -35
- package/src/ui/docs/content/carousel.md +37 -6
- package/src/ui/docs/content/chat.md +118 -37
- package/src/ui/docs/content/checkbox.md +73 -7
- package/src/ui/docs/content/cli.md +94 -55
- package/src/ui/docs/content/collapsible.md +43 -19
- package/src/ui/docs/content/command.md +30 -8
- package/src/ui/docs/content/composer.md +23 -8
- package/src/ui/docs/content/content.md +37 -7
- package/src/ui/docs/content/copyable.md +39 -8
- package/src/ui/docs/content/customization.md +55 -46
- package/src/ui/docs/content/cycle.md +33 -18
- package/src/ui/docs/content/data-products.md +115 -0
- package/src/ui/docs/content/data-state.md +20 -6
- package/src/ui/docs/content/data.md +20 -19
- package/src/ui/docs/content/detail.md +12 -5
- package/src/ui/docs/content/dialog.md +219 -28
- package/src/ui/docs/content/dictionary-value.md +4 -4
- package/src/ui/docs/content/dock.md +54 -19
- package/src/ui/docs/content/dot.md +35 -5
- package/src/ui/docs/content/drawer.md +193 -22
- package/src/ui/docs/content/empty-value.md +20 -6
- package/src/ui/docs/content/empty.md +24 -3
- package/src/ui/docs/content/events.md +76 -16
- package/src/ui/docs/content/executions.md +9 -9
- package/src/ui/docs/content/field.md +21 -6
- package/src/ui/docs/content/getting-started.md +44 -34
- package/src/ui/docs/content/icon-picker.md +19 -13
- package/src/ui/docs/content/icon-tile.md +28 -5
- package/src/ui/docs/content/input-otp.md +58 -7
- package/src/ui/docs/content/input.md +22 -3
- package/src/ui/docs/content/item.md +53 -6
- package/src/ui/docs/content/kbd.md +5 -1
- package/src/ui/docs/content/label.md +13 -4
- package/src/ui/docs/content/log.md +5 -5
- package/src/ui/docs/content/markdown.md +5 -4
- package/src/ui/docs/content/mcp.md +49 -23
- package/src/ui/docs/content/menu.md +95 -10
- package/src/ui/docs/content/metric-card.md +68 -21
- package/src/ui/docs/content/observability.md +10 -9
- package/src/ui/docs/content/page.md +125 -11
- package/src/ui/docs/content/pagination.md +13 -5
- package/src/ui/docs/content/pane.md +300 -0
- package/src/ui/docs/content/popover.md +42 -5
- package/src/ui/docs/content/presentation.md +91 -47
- package/src/ui/docs/content/progress.md +27 -14
- package/src/ui/docs/content/queue.md +39 -21
- package/src/ui/docs/content/radio-group.md +26 -4
- package/src/ui/docs/content/router.md +29 -10
- package/src/ui/docs/content/runtime.md +90 -54
- package/src/ui/docs/content/scheduler.md +53 -21
- package/src/ui/docs/content/scroll-area.md +42 -35
- package/src/ui/docs/content/select.md +78 -33
- package/src/ui/docs/content/semantic-context.md +21 -2
- package/src/ui/docs/content/separator.md +15 -9
- package/src/ui/docs/content/sidebar.md +25 -45
- package/src/ui/docs/content/skeleton.md +13 -4
- package/src/ui/docs/content/slider.md +37 -9
- package/src/ui/docs/content/spinner.md +4 -3
- package/src/ui/docs/content/split.md +34 -20
- package/src/ui/docs/content/storage.md +8 -9
- package/src/ui/docs/content/switch.md +10 -7
- package/src/ui/docs/content/table.md +46 -8
- package/src/ui/docs/content/tabs.md +14 -4
- package/src/ui/docs/content/testing.md +76 -31
- package/src/ui/docs/content/textarea.md +5 -4
- package/src/ui/docs/content/toast.md +48 -7
- package/src/ui/docs/content/toggle.md +3 -3
- package/src/ui/docs/content/tokens.md +15 -26
- package/src/ui/docs/content/tooltip.md +18 -2
- package/src/ui/docs/content/truncate.md +36 -6
- package/src/ui/docs/content/ui.md +74 -22
- package/src/ui/docs/content/upgrading.md +45 -36
- package/src/ui/docs/plugin.ts +36 -2
- package/src/ui/docs/registry.tsx +14 -2
- package/src/ui/drivers/react.tsx +16 -8
- package/src/ui/meta.ts +38 -32
- package/src/ui/react.tsx +14 -0
- package/src/vite/design.ts +16 -4
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
## Conversa com resposta integral
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Chat` para uma conversa completa: transcript, composer, indicador de atividade e rolagem
|
|
4
|
+
automática até a última mensagem. No modo mais simples, o componente guarda as mensagens e chama
|
|
5
|
+
`send` com o histórico a cada envio; o texto devolvido entra no transcript como resposta. Dê altura
|
|
6
|
+
ao contêiner, porque o transcript rola dentro dele.
|
|
4
7
|
|
|
5
8
|
```tsx preview
|
|
6
9
|
<div className="h-96 rounded-lg border">
|
|
7
10
|
<Chat
|
|
8
|
-
greeting="
|
|
11
|
+
greeting="Pergunte sobre os workspaces da sua equipe."
|
|
9
12
|
send={async (messages) => {
|
|
10
13
|
await new Promise((r) => setTimeout(r, 500))
|
|
11
14
|
const last = messages[messages.length - 1]
|
|
@@ -15,12 +18,17 @@ Um chat mínimo: lista de mensagens + composer. A conversa é gerenciada por den
|
|
|
15
18
|
</div>
|
|
16
19
|
```
|
|
17
20
|
|
|
21
|
+
**Enter** envia e **Shift+Enter** quebra a linha. Com o composer vazio, **↑** recupera as mensagens
|
|
22
|
+
anteriores da pessoa e **↓** volta em direção ao rascunho; durante a edição, as setas continuam
|
|
23
|
+
movendo o cursor. `greeting` ocupa o transcript vazio, some quando a conversa começa e não entra no
|
|
24
|
+
histórico enviado a `send`.
|
|
25
|
+
|
|
18
26
|
## Estado vazio composto
|
|
19
27
|
|
|
20
28
|
Quando uma frase não basta — marca, nome do agente e saudação administrável, por exemplo — o
|
|
21
29
|
app compõe o vazio com os slots de `Empty` e entrega em `empty`. O nó ocupa o mesmo lugar do
|
|
22
|
-
`greeting`, vence quando os dois existem e some quando a conversa começa.
|
|
23
|
-
|
|
30
|
+
`greeting`, vence quando os dois existem e some quando a conversa começa. Não envolva o conteúdo em
|
|
31
|
+
`Empty`: o chat já é a estrutura, e a moldura tracejada é para região de criação.
|
|
24
32
|
|
|
25
33
|
```tsx preview
|
|
26
34
|
<div className="h-72 rounded-lg border">
|
|
@@ -30,8 +38,8 @@ em volta: o chat já é a estrutura, e a moldura tracejada é para região de cr
|
|
|
30
38
|
<EmptyMedia variant="icon">
|
|
31
39
|
<Sparkles />
|
|
32
40
|
</EmptyMedia>
|
|
33
|
-
<EmptyTitle>
|
|
34
|
-
<EmptyDescription>Pergunte sobre
|
|
41
|
+
<EmptyTitle>Assistente da equipe</EmptyTitle>
|
|
42
|
+
<EmptyDescription>Pergunte sobre os workspaces, os membros e as atividades da equipe.</EmptyDescription>
|
|
35
43
|
</EmptyHeader>
|
|
36
44
|
}
|
|
37
45
|
send={async () => 'Pronto.'}
|
|
@@ -39,40 +47,52 @@ em volta: o chat já é a estrutura, e a moldura tracejada é para região de cr
|
|
|
39
47
|
</div>
|
|
40
48
|
```
|
|
41
49
|
|
|
42
|
-
## O backend
|
|
50
|
+
## O backend
|
|
43
51
|
|
|
44
|
-
O `send` posta a conversa no
|
|
52
|
+
O `send` posta a conversa no endpoint do aplicativo, que resolve o usuário e executa o agente
|
|
53
|
+
sobre as actions expostas à IA, com a autorização desse usuário:
|
|
45
54
|
|
|
46
55
|
```ts
|
|
47
56
|
// Rota do app (server): recebe { messages }, devolve { reply }.
|
|
48
57
|
async function chatRoute(req) {
|
|
49
|
-
const base = await auth.resolveContext(req) //
|
|
58
|
+
const base = await auth.resolveContext(req) // Quem é o usuário.
|
|
50
59
|
const { messages } = req.body
|
|
51
|
-
const { text } = await runtime.aiFor(base)!.run(messages) //
|
|
60
|
+
const { text } = await runtime.aiFor(base)!.run(messages) // O agente usa as actions.
|
|
52
61
|
return { reply: text }
|
|
53
62
|
}
|
|
54
63
|
```
|
|
55
64
|
|
|
56
65
|
```tsx
|
|
57
|
-
// No cliente, o send
|
|
66
|
+
// No cliente, o send chama essa rota:
|
|
58
67
|
<Chat send={async (messages) => (await api.post('/chat', { messages })).reply} />
|
|
59
68
|
```
|
|
60
69
|
|
|
61
|
-
O agente só executa actions
|
|
70
|
+
O agente só executa actions expostas à IA (`ai: true` ou um objeto `ai` sem `enabled: false`) e,
|
|
71
|
+
nelas, aplica o `authorize` com o contexto do usuário; uma action sem `authorize` fica aberta a qualquer usuário autenticado. Actions com
|
|
72
|
+
`ai.destructive` ou `ai.requiresConfirmation` são recusadas nesse exemplo, porque ele não passa
|
|
73
|
+
`confirm`; para aprovar essas chamadas, use `run(messages, { confirm })`. Para expor uma action ao
|
|
74
|
+
agente e conhecer as demais opções do runtime, consulte a página de [IA generativa](/docs/ai).
|
|
75
|
+
|
|
76
|
+
## Streaming de eventos
|
|
77
|
+
|
|
78
|
+
`send` também pode devolver um `AsyncIterable<ChatEvent>`. Assim, a resposta chega aos poucos e a
|
|
79
|
+
conversa mostra o que o agente está fazendo. O protocolo tem quatro eventos:
|
|
62
80
|
|
|
63
|
-
|
|
81
|
+
- `{ type: 'text', delta }` acrescenta um trecho à resposta em andamento.
|
|
82
|
+
- `{ type: 'tool', name, detail? }` informa a ferramenta em uso. O Chat mostra esse passo no
|
|
83
|
+
indicador de atividade, com o rótulo de `humanizeTool`, e não o grava no transcript.
|
|
84
|
+
- `{ type: 'artifact', kind, ref, title? }` acrescenta ao transcript algo produzido na conversa,
|
|
85
|
+
apresentado por `renderArtifact` ou, sem ele, como link.
|
|
86
|
+
- `{ type: 'done', ok, error? }` encerra o turno; com `ok: false`, o transcript recebe a mensagem de
|
|
87
|
+
erro.
|
|
64
88
|
|
|
65
|
-
|
|
66
|
-
`
|
|
67
|
-
**indicador vivo** (pontinhos + rótulo humano — nunca uma mensagem no transcript) e
|
|
68
|
-
artefatos produzidos na conversa rendem pelo `renderArtifact` do app. O backend liga em
|
|
69
|
-
`runtime.aiFor(base).runStream(...)` e streama por SSE; o `Promise<string>` de sempre
|
|
70
|
-
continua valendo — é o caso degenerado do protocolo.
|
|
89
|
+
No backend, `runtime.aiFor(base).runStream(...)` emite esses eventos, que podem seguir por SSE.
|
|
90
|
+
Devolver `Promise<string>` continua válido: equivale a um único `text` seguido de `done`.
|
|
71
91
|
|
|
72
92
|
```tsx preview
|
|
73
93
|
<div className="h-96 rounded-lg border">
|
|
74
94
|
<Chat
|
|
75
|
-
greeting="
|
|
95
|
+
greeting="Pergunte algo e acompanhe a resposta chegando aos poucos."
|
|
76
96
|
send={async function* (messages) {
|
|
77
97
|
yield { type: 'tool', name: 'query' }
|
|
78
98
|
await new Promise((r) => setTimeout(r, 600))
|
|
@@ -88,32 +108,93 @@ continua valendo — é o caso degenerado do protocolo.
|
|
|
88
108
|
</div>
|
|
89
109
|
```
|
|
90
110
|
|
|
91
|
-
|
|
92
|
-
componente ao trocar de conversa.
|
|
93
|
-
|
|
111
|
+
Para retomar uma conversa salva, passe `initialMessages` com o histórico persistido e troque a
|
|
112
|
+
`key` do componente ao trocar de conversa. `kickoff` inicia a conversa pelo assistente, uma vez,
|
|
113
|
+
quando o transcript nasce vazio.
|
|
94
114
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
## Conversa controlada pelo aplicativo
|
|
116
|
+
|
|
117
|
+
Quando o aplicativo já é dono do transcript — por exemplo, porque o servidor mantém a conversa e a
|
|
118
|
+
reenvia a cada reconexão —, passe `messages` em vez de `send`. Nesse modo, `onSend` recebe o texto
|
|
119
|
+
enviado, `busy` bloqueia novos envios enquanto o turno corre e `activity` controla o indicador.
|
|
120
|
+
`busy` não bloqueia a digitação: a pessoa pode escrever a próxima mensagem enquanto espera.
|
|
121
|
+
Retornar `false` em `onSend` devolve o texto ao composer, para que nada se perca em uma falha de
|
|
122
|
+
envio.
|
|
123
|
+
|
|
124
|
+
Os itens podem carregar `id` e `createdAt`. `renderMessageActions` acrescenta ações discretas às
|
|
125
|
+
mensagens enviadas e recebidas, visíveis em hover ou foco. O slot não busca dados: carregue
|
|
126
|
+
informação complementar somente depois da interação da pessoa e aplique novamente a autorização.
|
|
127
|
+
No exemplo, `activity=""` mostra somente os pontos, sem rótulo, e cada mensagem oferece a cópia do
|
|
128
|
+
texto.
|
|
129
|
+
|
|
130
|
+
```tsx preview
|
|
131
|
+
const nextId = useRef(3)
|
|
132
|
+
const [items, setItems] = useState<ChatTranscriptItem[]>([
|
|
133
|
+
{ id: '1', role: 'user', content: 'Qual é o status da Empresa X?' },
|
|
134
|
+
{ id: '2', role: 'assistant', content: 'A Empresa X está **ativa**, com 3 agentes.' },
|
|
135
|
+
])
|
|
136
|
+
const [busy, setBusy] = useState(false)
|
|
137
|
+
|
|
138
|
+
render(
|
|
139
|
+
<div className="h-96 rounded-lg border">
|
|
140
|
+
<Chat
|
|
141
|
+
messages={items}
|
|
142
|
+
busy={busy}
|
|
143
|
+
activity={busy ? '' : undefined}
|
|
144
|
+
onSend={async (text) => {
|
|
145
|
+
setItems((list) => [...list, { id: String(nextId.current++), role: 'user', content: text }])
|
|
146
|
+
setBusy(true)
|
|
147
|
+
await new Promise((r) => setTimeout(r, 800))
|
|
148
|
+
setItems((list) => [
|
|
149
|
+
...list,
|
|
150
|
+
{ id: String(nextId.current++), role: 'assistant', content: 'Mensagem registrada.' },
|
|
151
|
+
])
|
|
152
|
+
setBusy(false)
|
|
153
|
+
}}
|
|
154
|
+
renderMessageActions={(message) => (
|
|
155
|
+
<Copyable
|
|
156
|
+
value={message.content}
|
|
157
|
+
aria-label="Copiar mensagem"
|
|
158
|
+
className="text-muted-foreground hover:text-foreground"
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
161
|
+
/>
|
|
162
|
+
</div>,
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`activity` aceita `undefined`, que esconde o indicador; `null`, que mostra “Pensando…”; uma string
|
|
167
|
+
com o rótulo já humanizado pelo aplicativo; ou `''`, que mostra somente os pontos e mantém
|
|
168
|
+
“Pensando…” como nome acessível. No modo autogerenciado, o Chat decide o indicador sozinho e ignora
|
|
169
|
+
`busy` e `activity`.
|
|
99
170
|
|
|
100
171
|
## Propriedades de Chat
|
|
101
172
|
|
|
102
173
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
103
174
|
|---|---|---|---|
|
|
104
|
-
| `send` | `(messages: ChatMessage[]) => Promise<string> \| AsyncIterable<ChatEvent>` | | Modo autogerenciado:
|
|
105
|
-
| `messages` | `ChatTranscriptItem[]` | | Modo controlado: o transcript vem do
|
|
175
|
+
| `send` | `(messages: ChatMessage[]) => Promise<string> \| AsyncIterable<ChatEvent>` | | Modo autogerenciado: recebe o histórico com a nova mensagem e devolve a resposta inteira ou um stream de eventos. Ignorado no modo controlado. |
|
|
176
|
+
| `messages` | `ChatTranscriptItem[]` | | Modo controlado: o transcript vem do aplicativo. Com ele, `onSend`, `busy` e `activity` passam a valer. |
|
|
106
177
|
| `onSend` | `(text: string) => void \| boolean \| Promise<void \| boolean>` | | Modo controlado: recebe o texto enviado; retornar `false` devolve o texto ao composer. |
|
|
107
|
-
| `busy` | `boolean` | | Modo controlado:
|
|
108
|
-
| `activity` | `string \| null` | `undefined` |
|
|
109
|
-
| `notice` | `ReactNode` | | Aviso do
|
|
110
|
-
| `composerActions` | `ReactNode` | | Seletores discretos na barra do composer
|
|
178
|
+
| `busy` | `boolean` | `false` | Modo controlado: bloqueia o envio enquanto o turno corre; a digitação continua liberada. |
|
|
179
|
+
| `activity` | `string \| null` | `undefined` | Modo controlado: indicador de atividade. `undefined` esconde, `null` mostra “Pensando…”, uma string mostra o rótulo e `''` mostra somente os pontos. |
|
|
180
|
+
| `notice` | `ReactNode` | | Aviso do aplicativo acima do composer, como credencial expirada ou agente desatualizado. |
|
|
181
|
+
| `composerActions` | `ReactNode` | | Seletores discretos na barra do composer, como agente, aplicativo ou escopo. |
|
|
111
182
|
| `composerClassName` | `string` | | Ajusta o contêiner externo do composer sem alcançar o DOM interno. |
|
|
112
183
|
| `greeting` | `string` | | Texto do estado vazio; some quando a conversa começa. |
|
|
113
|
-
| `empty` | `ReactNode` | | Estado vazio composto pelo
|
|
184
|
+
| `empty` | `ReactNode` | | Estado vazio composto pelo aplicativo; vence `greeting` quando os dois existem. |
|
|
114
185
|
| `initialMessages` | `ChatMessage[]` | | Histórico inicial do modo autogerenciado; troque a `key` ao trocar de conversa. |
|
|
115
186
|
| `kickoff` | `() => Promise<string> \| AsyncIterable<ChatEvent>` | | Conversa que começa pelo assistente, uma vez, quando o transcript nasce vazio. |
|
|
116
|
-
| `renderArtifact` | `(artifact: ChatArtifact) => ReactNode` | link com o título |
|
|
117
|
-
| `renderMessageActions` | `(message: ChatTranscriptMessage) => ReactNode` | | Ações contextuais
|
|
118
|
-
| `humanizeTool` | `(name: string, detail?: string) => string` | pt-BR
|
|
187
|
+
| `renderArtifact` | `(artifact: ChatArtifact) => ReactNode` | link com o título | Apresentação do evento `artifact`. |
|
|
188
|
+
| `renderMessageActions` | `(message: ChatTranscriptMessage) => ReactNode` | | Ações contextuais de mensagens enviadas e recebidas; ficam visíveis em hover ou foco. |
|
|
189
|
+
| `humanizeTool` | `(name: string, detail?: string) => string` | rótulos em pt-BR | Rótulo do indicador para o evento `tool` no modo autogerenciado. |
|
|
119
190
|
| `placeholder` | `string` | `'Escreva uma mensagem…'` | Placeholder do composer. |
|
|
191
|
+
| `className` | `string` | | Classes da raiz, que ocupa a altura do contêiner (`h-full`). |
|
|
192
|
+
|
|
193
|
+
## Tipos do transcript
|
|
194
|
+
|
|
195
|
+
| Tipo | Forma | Uso |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `ChatMessage` | `{ id?, createdAt?, role: 'user' \| 'assistant', content }` | Histórico enviado a `send` e aceito por `initialMessages`. `createdAt` é um instante ISO-8601. |
|
|
198
|
+
| `ChatTranscriptItem` | `ChatMessage` com `role` também `'error'`, ou `{ id?, createdAt?, role: 'artifact', artifact: ChatArtifact }` | Itens de `messages` no modo controlado. Mensagens `error` aparecem destacadas no transcript. |
|
|
199
|
+
| `ChatTranscriptMessage` | Os itens de `ChatTranscriptItem` que têm `content` | Argumento de `renderMessageActions`. |
|
|
200
|
+
| `ChatArtifact` | `{ kind, ref, title? }` | Algo produzido na conversa, como um link ou um relatório; `ref` é o endereço ou identificador. |
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
## Escolha booleana
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Checkbox` quando a pessoa precisar marcar uma opção que só vale ao salvar um formulário, como
|
|
4
|
+
aceitar uma condição ou incluir uma etapa. Para uma preferência que entra em vigor no clique, use
|
|
5
|
+
[`Switch`](/ui/switch).
|
|
6
|
+
|
|
7
|
+
Associe cada caixa a um [`Label`](/ui/label) pelo par `htmlFor` e `id`: clicar no texto também
|
|
8
|
+
alterna a caixa. `defaultChecked` define o estado inicial no modo não controlado.
|
|
4
9
|
|
|
5
10
|
```tsx preview
|
|
6
11
|
<div className="flex items-center gap-2">
|
|
@@ -11,7 +16,8 @@ Sempre em par com Label (htmlFor↔id) — clicar no texto alterna a caixa. defa
|
|
|
11
16
|
|
|
12
17
|
## Controlado
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
Use `checked` e `onCheckedChange` quando o estado pertencer ao consumidor. O callback recebe
|
|
20
|
+
`boolean` ou `'indeterminate'`; compare com `true` para guardar um booleano.
|
|
15
21
|
|
|
16
22
|
```tsx preview
|
|
17
23
|
const [autoReview, setAutoReview] = useState(true)
|
|
@@ -30,7 +36,8 @@ render(
|
|
|
30
36
|
|
|
31
37
|
## Lista de opções
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
Quando várias caixas alimentam um único valor, guarde a lista dos itens marcados. É o caso de
|
|
40
|
+
escolher as habilidades de um agente.
|
|
34
41
|
|
|
35
42
|
```tsx preview col-start
|
|
36
43
|
const [skills, setSkills] = useState(['clean-code', 'test'])
|
|
@@ -50,9 +57,53 @@ render(
|
|
|
50
57
|
)
|
|
51
58
|
```
|
|
52
59
|
|
|
60
|
+
## Seleção parcial
|
|
61
|
+
|
|
62
|
+
Quando uma caixa controla um grupo, como “Selecionar todas”, ela precisa mostrar também o caso em
|
|
63
|
+
que só parte do grupo está marcada. Passe `checked="indeterminate"` nesse caso: a caixa fica
|
|
64
|
+
preenchida e mostra um traço no lugar do visto, e leitores de tela a anunciam como parcialmente
|
|
65
|
+
marcada. Ao clicar em uma caixa indeterminada, `onCheckedChange` recebe `true`.
|
|
66
|
+
|
|
67
|
+
```tsx preview col-start
|
|
68
|
+
const all = ['clean-code', 'code-style', 'ddd', 'test']
|
|
69
|
+
const [skills, setSkills] = useState(['clean-code', 'test'])
|
|
70
|
+
const allState = skills.length === all.length ? true : skills.length > 0 ? 'indeterminate' : false
|
|
71
|
+
const toggleSkill = (skill: string): void => {
|
|
72
|
+
setSkills((prev) => (prev.includes(skill) ? prev.filter((s) => s !== skill) : [...prev, skill]))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
render(
|
|
76
|
+
<>
|
|
77
|
+
<div className="flex items-center gap-2">
|
|
78
|
+
<Checkbox
|
|
79
|
+
id="skills-all"
|
|
80
|
+
checked={allState}
|
|
81
|
+
onCheckedChange={(v) => setSkills(v === true ? all : [])}
|
|
82
|
+
/>
|
|
83
|
+
<Label htmlFor="skills-all">Selecionar todas</Label>
|
|
84
|
+
</div>
|
|
85
|
+
<div className="flex flex-col gap-2 pl-6">
|
|
86
|
+
{all.map((skill) => (
|
|
87
|
+
<div key={skill} className="flex items-center gap-2">
|
|
88
|
+
<Checkbox
|
|
89
|
+
id={`partial-${skill}`}
|
|
90
|
+
checked={skills.includes(skill)}
|
|
91
|
+
onCheckedChange={() => toggleSkill(skill)}
|
|
92
|
+
/>
|
|
93
|
+
<Label htmlFor={`partial-${skill}`}>{skill}</Label>
|
|
94
|
+
</div>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
</>,
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Com o grupo inteiro marcado, o clique em “Selecionar todas” recebe `false` e limpa a lista.
|
|
102
|
+
|
|
53
103
|
## Desabilitado
|
|
54
104
|
|
|
55
|
-
disabled esmaece a caixa
|
|
105
|
+
`disabled` esmaece e bloqueia a caixa, marcada ou não. O `Label` escrito logo depois da caixa, no
|
|
106
|
+
mesmo contêiner, esmaece junto.
|
|
56
107
|
|
|
57
108
|
```tsx preview col-start
|
|
58
109
|
<div className="flex items-center gap-2">
|
|
@@ -65,11 +116,26 @@ disabled esmaece a caixa e o rótulo em par (peer-disabled no Label) — marcado
|
|
|
65
116
|
</div>
|
|
66
117
|
```
|
|
67
118
|
|
|
119
|
+
## Estado inválido
|
|
120
|
+
|
|
121
|
+
Quando a marcação for obrigatória e estiver ausente, declare `aria-invalid` na caixa e explique o
|
|
122
|
+
que falta com `FieldError`. `data-invalid="true"` no [`Field`](/ui/field) leva o contexto de erro
|
|
123
|
+
também ao rótulo.
|
|
124
|
+
|
|
125
|
+
```tsx preview col-start
|
|
126
|
+
<Field orientation="horizontal" data-invalid="true">
|
|
127
|
+
<Checkbox id="accept-terms" aria-invalid aria-describedby="accept-terms-error" />
|
|
128
|
+
<FieldLabel htmlFor="accept-terms">Li e aceito os termos de uso</FieldLabel>
|
|
129
|
+
</Field>
|
|
130
|
+
<FieldError id="accept-terms-error">Aceite os termos para continuar.</FieldError>
|
|
131
|
+
```
|
|
132
|
+
|
|
68
133
|
## Propriedades de Checkbox
|
|
69
134
|
|
|
70
135
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
71
136
|
|---|---|---|---|
|
|
72
|
-
| `checked` | `boolean \| 'indeterminate'` | |
|
|
73
|
-
| `onCheckedChange` | `(checked: boolean \| 'indeterminate') => void` | | Chamado a cada alternância. Para guardar um
|
|
137
|
+
| `checked` | `boolean \| 'indeterminate'` | | Estado no modo controlado. Use com `onCheckedChange`. |
|
|
138
|
+
| `onCheckedChange` | `(checked: boolean \| 'indeterminate') => void` | | Chamado a cada alternância. Para guardar um booleano, compare com `true`. |
|
|
74
139
|
| `defaultChecked` | `boolean` | `false` | Estado inicial no modo não controlado. |
|
|
75
|
-
| `disabled` | `boolean` | `false` | Esmaece e bloqueia
|
|
140
|
+
| `disabled` | `boolean` | `false` | Esmaece e bloqueia a caixa; o `Label` irmão posterior esmaece junto. |
|
|
141
|
+
| `aria-invalid` | `boolean` | | Aplica a borda e o anel de erro. |
|
|
@@ -4,14 +4,14 @@ title: CLI opus
|
|
|
4
4
|
|
|
5
5
|
# CLI opus
|
|
6
6
|
|
|
7
|
-
O
|
|
8
|
-
valida as convenções e
|
|
9
|
-
comandos e `opus <comando> --help` detalha cada um.
|
|
7
|
+
O CLI `opus` acompanha o projeto do início à entrega: cria o app, gera artefatos a partir das
|
|
8
|
+
declarações, valida as convenções e oferece a estrutura do projeto a agentes por MCP. `opus --help`
|
|
9
|
+
lista os comandos e `opus <comando> --help` detalha cada um.
|
|
10
10
|
|
|
11
11
|
## Gates
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
Use os gates no CI e antes de entregar uma mudança. Cada um informa o que precisa de ajuste e
|
|
14
|
+
encerra com sucesso (exit 0) quando não encontra violação.
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
opus check src # convenções das actions e da UI (exit ≠ 0 se violar)
|
|
@@ -20,33 +20,37 @@ opus db check # drift entidade ↔ banco (read-only)
|
|
|
20
20
|
opus seed check # bindings, dependências, ciclos e scripts paralelos de seed
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
`opus check` lê o
|
|
24
|
-
(`action-name`, `kind`, `field-order`, `export`, `requires-sem-authorize`), UI
|
|
25
|
-
(`ui-structure`, `ui-semantic-api`, `removed-ui-token`, `unpaired-ui-surface`) e
|
|
26
|
-
(`data-product-export`, `-id`, `-duplicate`, `-version`,
|
|
27
|
-
e `-entity`, todas com o prefixo `data-product`). Um projeto
|
|
28
|
-
marcado com `opus.json` e ainda sem actions passa
|
|
29
|
-
falha, porque um gate
|
|
23
|
+
`opus check` lê o código-fonte sem executá-lo e aplica regras em três grupos: actions
|
|
24
|
+
(`action-name`, `kind`, `field-order`, `export`, `requires-sem-authorize`, `ignored-by-kind`), UI
|
|
25
|
+
(`ui-structure`, `ui-structure-split`, `ui-semantic-api`, `removed-ui-token`, `unpaired-ui-surface`) e
|
|
26
|
+
[Produtos de Dados](/docs/data-products) (`data-product-export`, `-id`, `-duplicate`, `-version`,
|
|
27
|
+
`-interfaces`, `-interface`, `-entities` e `-entity`, todas com o prefixo `data-product`). Um projeto
|
|
28
|
+
marcado com `opus.json` e ainda sem actions passa sem achados; sem o marcador, nenhuma action
|
|
29
|
+
encontrada é falha, porque um gate que não verificou nada não é aprovação. `opus check --help` descreve cada regra.
|
|
30
30
|
|
|
31
31
|
### Hook de pré-push
|
|
32
32
|
|
|
33
|
-
O hook Git que `opus setup` instala
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`opus
|
|
37
|
-
|
|
33
|
+
O hook Git que `opus setup` instala executa, antes de cada push:
|
|
34
|
+
|
|
35
|
+
1. `opus copy --check`;
|
|
36
|
+
2. `opus pre-push materialization` na raiz, que confirma se os artefatos Opus materializados
|
|
37
|
+
(skills, hooks e instruções) correspondem à versão adotada;
|
|
38
|
+
3. `opus check` em cada app com `opus.json`.
|
|
39
|
+
|
|
40
|
+
Qualquer comando com exit ≠ 0 bloqueia o push. Para antecipar o resultado, execute os mesmos
|
|
41
|
+
comandos manualmente:
|
|
38
42
|
|
|
39
43
|
```bash
|
|
40
44
|
opus pre-push # o mesmo opus check no diretório atual
|
|
41
45
|
opus pre-push apps/portal # o check de um app específico
|
|
42
|
-
opus pre-push materialization # só
|
|
46
|
+
opus pre-push materialization # só confere se os artefatos materializados estão atualizados
|
|
43
47
|
```
|
|
44
48
|
|
|
45
49
|
## Seeds de desenvolvimento e teste
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
Seeds registrados no `opus.config.ts` têm perfis, métricas e escopos explícitos. Listagem e gate
|
|
52
|
+
não abrem o banco; planejamento, aplicação e verificação só conectam com `NODE_ENV=development` ou
|
|
53
|
+
`NODE_ENV=test`.
|
|
50
54
|
|
|
51
55
|
```bash
|
|
52
56
|
opus seed list
|
|
@@ -58,7 +62,7 @@ NODE_ENV=development opus seed verify customers.scenarios --profile smoke --scop
|
|
|
58
62
|
Com qualquer outro valor de `NODE_ENV`, inclusive ausente, esses três comandos falham antes de
|
|
59
63
|
abrir a conexão. O escopo informado também precisa constar em `safety.scopes` do seed.
|
|
60
64
|
|
|
61
|
-
`--profile` escolhe o perfil (
|
|
65
|
+
`--profile` escolhe o perfil (padrão: o `defaultProfile` do seed) e `--scope` declara o escopo
|
|
62
66
|
dos dados (a variável `OPUS_SEED_SCOPE` é a alternativa). `--json` devolve o resultado
|
|
63
67
|
estruturado, inclusive em caso de erro. `apply` converge quando repetido; não há reset ou
|
|
64
68
|
truncate no contrato. A skill `$create-opus-seed` estrutura um novo dataset, e
|
|
@@ -66,33 +70,47 @@ truncate no contrato. A skill `$create-opus-seed` estrutura um novo dataset, e
|
|
|
66
70
|
|
|
67
71
|
## Geração e introspecção
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
As declarações, incluindo a `description` de entidades e actions, são a fonte; `opus gen` as
|
|
74
|
+
projeta. O manifest é versionado como um lockfile, e sua diferença mostra na revisão o que mudou
|
|
75
|
+
no contrato.
|
|
71
76
|
|
|
72
77
|
```bash
|
|
73
78
|
opus gen # manifest / openapi / docs / stubs a partir do opus.config.ts
|
|
74
79
|
opus gen --config ./apps/api/opus.config.ts # config fora do diretório atual
|
|
75
|
-
opus gen --output ./custom/gen # pasta de saída (
|
|
80
|
+
opus gen --output ./custom/gen # pasta de saída (padrão: a do config, ou ./.gen)
|
|
76
81
|
opus copy # inventário semântico de copy dos contratos
|
|
77
|
-
opus introspect # modelo da estrutura
|
|
82
|
+
opus introspect # modelo da estrutura: actions, reactions, schedules e ligações
|
|
78
83
|
opus introspect --json
|
|
79
84
|
```
|
|
80
85
|
|
|
81
86
|
`opus copy` projeta os textos de `defineAction`/`defineContract` no inventário que a política de
|
|
82
|
-
copy da `@softize/base` valida. O caminho vem de `base.json` (`copy.inventory`;
|
|
87
|
+
copy da `@softize/base` valida. O caminho vem de `base.json` (`copy.inventory`; padrão
|
|
83
88
|
`.base/copy-inventory.json` na raiz Git), a escrita é atômica e o arquivo gerado deve ser
|
|
84
89
|
versionado. `--check` compara sem escrever e falha quando o inventário está ausente,
|
|
85
90
|
desatualizado ou contém copy dinâmica não declarada.
|
|
86
91
|
|
|
92
|
+
`opus introspect` lê o código sem executá-lo e só reconhece `defineAction`, `defineReaction`,
|
|
93
|
+
`defineSchedule` e `defineDataProduct`. Actions do par `defineContract` e `bindAction`, reactions do
|
|
94
|
+
par `defineReactionContract` e `bindReaction` e schedules declarados sem `defineSchedule` ficam fora
|
|
95
|
+
do modelo. Para conferir as actions do split, use `opus check`.
|
|
96
|
+
|
|
87
97
|
`--config` vale para `gen`, `db` e `seed` e sempre aponta para um caminho dentro do projeto. O
|
|
88
|
-
`gen` aceita `--force` por compatibilidade, mas a flag não altera nada: a saída
|
|
98
|
+
`gen` aceita `--force` por compatibilidade, mas a flag não altera nada: a saída fica em um
|
|
89
99
|
diretório dedicado e é sempre sobrescrita.
|
|
90
100
|
|
|
101
|
+
### Variáveis de ambiente
|
|
102
|
+
|
|
103
|
+
`gen`, `db` e `seed` carregam o `.env` que está ao lado do `opus.config.ts` antes de importar o
|
|
104
|
+
config. Assim, a conexão aberta pelo config usa as mesmas variáveis do projeto. Uma variável já
|
|
105
|
+
definida no ambiente vence o arquivo: `DATABASE_URL=… opus db migrate` usa a URL informada no
|
|
106
|
+
comando. A ausência do `.env` não é erro, porque containers e CI costumam injetar as variáveis
|
|
107
|
+
diretamente.
|
|
108
|
+
|
|
91
109
|
## Banco
|
|
92
110
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
Os comandos `db` carregam o `opus.config.ts` e abrem conexão; por isso rodam num processo separado,
|
|
112
|
+
com `tsx`. O padrão é um schema idempotente: um único script SQL que pode ser executado de novo a
|
|
113
|
+
cada mudança, em vez de migrations numeradas.
|
|
96
114
|
|
|
97
115
|
```bash
|
|
98
116
|
opus db check # compara o schema do banco com as entidades (read-only)
|
|
@@ -100,36 +118,57 @@ opus db migrate # aplica o schema idempotente e roda o drift-check na sequên
|
|
|
100
118
|
opus db scaffold # rascunho kysely a partir do diff, como referência para escrever o SQL
|
|
101
119
|
```
|
|
102
120
|
|
|
103
|
-
`db scaffold` gera um rascunho para revisão
|
|
104
|
-
down`
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
`opus
|
|
121
|
+
`db scaffold` gera um rascunho para revisão manual; a fonte continua sendo o script. `db migrate
|
|
122
|
+
down` foi descontinuado e encerra com erro: sem histórico de migrations, não há o que reverter.
|
|
123
|
+
Para desfazer uma mudança, edite o schema e rode `opus db migrate` de novo.
|
|
124
|
+
|
|
125
|
+
O `opus.config.ts` precisa expor a factory `database`, chamada só quando um comando precisa do
|
|
126
|
+
banco, as entidades ou os domínios e, opcionalmente, `schema` e `migrations`; `opus db --help`
|
|
127
|
+
detalha cada campo.
|
|
108
128
|
|
|
109
129
|
## Bootstrap
|
|
110
130
|
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
`create` gera um app novo com os pré-requisitos configurados. `setup` roda em cada app, pode ser
|
|
132
|
+
repetido e não sobrescreve arquivos do projeto.
|
|
113
133
|
|
|
114
134
|
```bash
|
|
115
|
-
opus create meu-app # app
|
|
116
|
-
opus create meu-cliente --monorepo # a
|
|
135
|
+
opus create meu-app # app novo: protocolo, UI, preview e automação
|
|
136
|
+
opus create meu-cliente --monorepo # a raiz de um workspace (apps/* + packages/*)
|
|
117
137
|
opus create apps/portal # dentro de um workspace: só o app (modo detectado)
|
|
118
138
|
opus setup # grava opus.json e materializa a camada específica do SDK
|
|
119
139
|
```
|
|
120
140
|
|
|
121
|
-
O esqueleto do `create`
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
depois de `pnpm run setup`.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
O esqueleto do `create` vem do mesmo pacote que o SDK e acompanha sua versão. O app nasce com os
|
|
142
|
+
gates aprovados: domínio de exemplo, teste, manifest e dev server que respeita a variável `PORT`,
|
|
143
|
+
para que um orquestrador de preview escolha a porta. O método geral, a memória e a revisão vêm da
|
|
144
|
+
Base depois de `pnpm run setup`.
|
|
145
|
+
|
|
146
|
+
O `create` detecta o modo. Fora de um workspace pnpm, gera o repositório inteiro. Dentro de um
|
|
147
|
+
workspace, gera só os arquivos do app e lista como aviso o que a raiz precisa ter, sem alterar os
|
|
148
|
+
arquivos dela.
|
|
149
|
+
|
|
150
|
+
## MCP para agentes de desenvolvimento
|
|
151
|
+
|
|
152
|
+
`opus mcp` inicia, por stdio, um servidor MCP para o agente que trabalha no código do projeto. Com
|
|
153
|
+
ele, o agente consulta a estrutura, valida convenções, gera actions no formato esperado e
|
|
154
|
+
consulta o catálogo de componentes. Esse servidor não executa actions do app; para expor actions a
|
|
155
|
+
agentes em produção, use o [servidor MCP do runtime](/docs/mcp).
|
|
156
|
+
|
|
157
|
+
| Tool | O que faz |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `opus_introspect` | Devolve o modelo de `opus introspect`, com os mesmos limites descritos acima. |
|
|
160
|
+
| `opus_check` | Executa as regras de `opus check` e devolve os achados. |
|
|
161
|
+
| `opus_create_action` | Devolve, sem gravar, o contrato e o binding iniciais de uma action a partir de recurso, verbo e kind. A skill `$create-opus-action` usa o mesmo gerador. |
|
|
162
|
+
| `opus_list_components` | Lista os componentes da UI, quando usar cada um e o que importar. |
|
|
163
|
+
| `opus_get_component` | Detalha um componente pelo nome. |
|
|
164
|
+
|
|
165
|
+
O setup não cria a configuração do cliente MCP. Em clientes que leem `.mcp.json` na raiz do
|
|
166
|
+
projeto, registre o servidor assim:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"mcpServers": {
|
|
171
|
+
"opus": { "command": "pnpm", "args": ["exec", "opus", "mcp"] }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|