@softize/opus 12.11.0 → 13.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 +29 -0
- package/bin/lib/check.mjs +2 -7
- package/bin/lib/copy.mjs +1 -5
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +93 -10
- package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
- package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
- package/docs/radius-scale.md +1 -1
- package/package.json +1 -1
- package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
- package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
- package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
- package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
- package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
- package/src/ui/components/patterns/confirm.tsx +140 -40
- package/src/ui/components/patterns/list.tsx +35 -40
- package/src/ui/components/patterns/page-state.tsx +2 -2
- package/src/ui/components/patterns/sidebar.tsx +26 -26
- package/src/ui/components/patterns/trigger.tsx +25 -22
- package/src/ui/components/primitives/alert.tsx +3 -3
- package/src/ui/components/primitives/dialog.tsx +196 -39
- package/src/ui/components/primitives/drawer.tsx +8 -5
- package/src/ui/components/primitives/empty.tsx +3 -3
- package/src/ui/components/primitives/item.tsx +3 -3
- package/src/ui/components/primitives/sonner.tsx +187 -8
- package/src/ui/docs/DocBrowser.tsx +102 -23
- package/src/ui/docs/content/accordion.md +22 -16
- package/src/ui/docs/content/action-form-card.md +8 -8
- package/src/ui/docs/content/action-form-dialog.md +9 -9
- package/src/ui/docs/content/action-form.md +28 -34
- package/src/ui/docs/content/action-list-dialog.md +11 -6
- package/src/ui/docs/content/action-list.md +64 -39
- package/src/ui/docs/content/action-trigger.md +21 -14
- package/src/ui/docs/content/action-view.md +8 -8
- package/src/ui/docs/content/actions.md +9 -9
- package/src/ui/docs/content/ai.md +3 -3
- package/src/ui/docs/content/alert.md +14 -12
- package/src/ui/docs/content/aspect-ratio.md +4 -4
- package/src/ui/docs/content/audit.md +2 -2
- package/src/ui/docs/content/auth.md +3 -3
- package/src/ui/docs/content/avatar.md +34 -14
- package/src/ui/docs/content/badge.md +3 -3
- package/src/ui/docs/content/breadcrumb.md +13 -8
- package/src/ui/docs/content/button.md +81 -6
- package/src/ui/docs/content/calendar.md +5 -5
- package/src/ui/docs/content/card.md +1 -1
- package/src/ui/docs/content/carousel.md +16 -11
- package/src/ui/docs/content/chat.md +3 -3
- package/src/ui/docs/content/checkbox.md +7 -7
- package/src/ui/docs/content/cli.md +5 -5
- package/src/ui/docs/content/collapsible.md +8 -8
- package/src/ui/docs/content/command.md +16 -8
- package/src/ui/docs/content/composer.md +2 -2
- package/src/ui/docs/content/content.md +2 -2
- package/src/ui/docs/content/copyable.md +4 -3
- package/src/ui/docs/content/customization.md +5 -5
- package/src/ui/docs/content/cycle.md +3 -3
- package/src/ui/docs/content/data-state.md +11 -12
- package/src/ui/docs/content/data.md +26 -33
- package/src/ui/docs/content/detail.md +3 -3
- package/src/ui/docs/content/dialog.md +339 -31
- package/src/ui/docs/content/dictionary-value.md +8 -8
- package/src/ui/docs/content/dock.md +3 -3
- package/src/ui/docs/content/drawer.md +27 -14
- package/src/ui/docs/content/empty-value.md +2 -2
- package/src/ui/docs/content/empty.md +19 -12
- package/src/ui/docs/content/events.md +4 -4
- package/src/ui/docs/content/field.md +34 -12
- package/src/ui/docs/content/getting-started.md +1 -1
- package/src/ui/docs/content/icon-picker.md +8 -4
- package/src/ui/docs/content/input-otp.md +20 -12
- package/src/ui/docs/content/input.md +121 -9
- package/src/ui/docs/content/item.md +27 -13
- package/src/ui/docs/content/kbd.md +19 -11
- package/src/ui/docs/content/label.md +5 -3
- package/src/ui/docs/content/log.md +4 -4
- package/src/ui/docs/content/markdown.md +7 -6
- package/src/ui/docs/content/mcp.md +13 -15
- package/src/ui/docs/content/menu.md +34 -16
- package/src/ui/docs/content/observability.md +2 -2
- package/src/ui/docs/content/page.md +51 -6
- package/src/ui/docs/content/pagination.md +22 -17
- package/src/ui/docs/content/popover.md +16 -8
- package/src/ui/docs/content/progress.md +7 -5
- package/src/ui/docs/content/queue.md +5 -5
- package/src/ui/docs/content/radio-group.md +20 -12
- package/src/ui/docs/content/router.md +11 -6
- package/src/ui/docs/content/scheduler.md +4 -5
- package/src/ui/docs/content/scroll-area.md +12 -7
- package/src/ui/docs/content/select.md +42 -29
- package/src/ui/docs/content/separator.md +5 -5
- package/src/ui/docs/content/sidebar.md +323 -54
- package/src/ui/docs/content/skeleton.md +3 -2
- package/src/ui/docs/content/slider.md +8 -7
- package/src/ui/docs/content/spinner.md +8 -8
- package/src/ui/docs/content/split.md +8 -5
- package/src/ui/docs/content/storage.md +6 -8
- package/src/ui/docs/content/switch.md +8 -7
- package/src/ui/docs/content/table.md +13 -3
- package/src/ui/docs/content/tabs.md +28 -14
- package/src/ui/docs/content/testing.md +9 -11
- package/src/ui/docs/content/textarea.md +5 -4
- package/src/ui/docs/content/toast.md +47 -13
- package/src/ui/docs/content/toggle.md +75 -7
- package/src/ui/docs/content/tokens.md +3 -3
- package/src/ui/docs/content/tooltip.md +19 -11
- package/src/ui/docs/content/truncate.md +7 -8
- package/src/ui/docs/content/ui.md +10 -9
- package/src/ui/docs/content/upgrading.md +7 -8
- package/src/ui/docs/registry.tsx +20 -37
- package/src/ui/meta.ts +64 -94
- package/src/ui/react.tsx +15 -16
- package/src/ui/theme.css +50 -0
- package/src/ui/components/primitives/alert-dialog.tsx +0 -192
- package/src/ui/docs/content/alert-dialog.md +0 -73
- package/src/ui/docs/content/button-group.md +0 -71
- package/src/ui/docs/content/confirm.md +0 -120
- package/src/ui/docs/content/input-group.md +0 -79
- package/src/ui/docs/content/page-state.md +0 -45
- package/src/ui/docs/content/toggle-group.md +0 -81
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Listagem derivada do contrato
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `ActionList` para apresentar uma coleção pesquisável descrita por uma `ListAction`. O contrato
|
|
4
|
+
define colunas, busca, filtros, período, ordenação e paginação; a interface materializa esses
|
|
5
|
+
recursos e envia o estado correspondente no input. Filtros avançados aparecem no modal “Filtros” e
|
|
6
|
+
os filtros aplicados permanecem visíveis como chips removíveis.
|
|
7
|
+
|
|
8
|
+
A barra se adapta ao espaço disponível. A busca cede largura primeiro e filtros que deixam de caber
|
|
9
|
+
migram para o modal; a linha só quebra quando nenhum controle restante puder ceder espaço.
|
|
4
10
|
|
|
5
11
|
```tsx preview col
|
|
6
12
|
render(
|
|
@@ -28,18 +34,16 @@ superfícies que não são listagens, como relatórios. O estado é controlado p
|
|
|
28
34
|
/>
|
|
29
35
|
```
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
somente quando declarados. Períodos incluem os presets do
|
|
35
|
-
contrato e o intervalo personalizado no mesmo calendário usado pela `ActionList`.
|
|
37
|
+
Seletores inline mantêm largura previsível (`w-40`; lookup e múltiplo usam `w-52`). Rótulos longos
|
|
38
|
+
são truncados no controle, mas permanecem completos na lista. No modal de filtros avançados, o
|
|
39
|
+
seletor ocupa toda a largura. Busca, filtros e período aparecem somente quando declarados.
|
|
36
40
|
|
|
37
41
|
## Colunas de dicionário
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
Quando o campo de saída usa `t.dict().zod()`, a coluna apresenta o valor com `DictionaryValue` e
|
|
44
|
+
respeita o papel declarado pelo dicionário. Classificações usam badge `outline`; status e estágio
|
|
45
|
+
usam badge tonal; `plain` e papéis ausentes permanecem como texto. Para um campo `string`, a coluna
|
|
46
|
+
pode indicar um dicionário registrado no provider:
|
|
43
47
|
`{ key: 'source', label: 'Fonte', dictionary: 'customerSource' }`. Dimensões independentes, como
|
|
44
48
|
tipo e estágio, ficam em colunas distintas — a leitura de comparação depende disso. `type:
|
|
45
49
|
'badge'` continua sendo o chip `outline` legado; com dicionário, ele mostra o rótulo em vez do
|
|
@@ -56,7 +60,8 @@ Renderer customizado reutiliza `EmptyValue`.
|
|
|
56
60
|
|
|
57
61
|
## Células custom (cells)
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
Use `cells` somente quando uma coluna precisar de apresentação própria, como link, composição ou
|
|
64
|
+
ação. A chave corresponde à `key` da coluna. Valores de dicionário não precisam desse override.
|
|
60
65
|
|
|
61
66
|
```tsx preview col
|
|
62
67
|
render(
|
|
@@ -78,9 +83,12 @@ render(
|
|
|
78
83
|
)
|
|
79
84
|
```
|
|
80
85
|
|
|
81
|
-
## Período
|
|
86
|
+
## Período obrigatório
|
|
82
87
|
|
|
83
|
-
|
|
88
|
+
Quando o contrato declara `periods`, a listagem sempre possui um recorte temporal. O preset marcado
|
|
89
|
+
com `default: true`, ou o primeiro da coleção, começa aplicado. Presets usam um valor relativo na
|
|
90
|
+
URL, como `?period=last7`; o padrão é omitido. Um intervalo personalizado usa diretamente as datas,
|
|
91
|
+
como `?period=2026-07-01..2026-07-07`, e chega ao handler pelos parâmetros `from` e `to`.
|
|
84
92
|
|
|
85
93
|
```tsx
|
|
86
94
|
periods: [
|
|
@@ -91,13 +99,20 @@ periods: [
|
|
|
91
99
|
// Presets computáveis: today · yesterday · last7 · last30 · thisMonth · lastMonth.
|
|
92
100
|
```
|
|
93
101
|
|
|
94
|
-
## Paginação e
|
|
102
|
+
## Paginação e carregamento
|
|
95
103
|
|
|
96
|
-
|
|
104
|
+
`ActionList` envia `limit`, definido por `pageSize`, e `page` no input. O handler aplica o recorte e
|
|
105
|
+
devolve `total` na resposta paginada. Quando existe total, o rodapé mostra a página atual, os links
|
|
106
|
+
disponíveis e a quantidade de itens. Alterar busca, filtro, ordenação ou período retorna à primeira
|
|
107
|
+
página. Durante a primeira consulta, `DataState` apresenta o carregamento; em atualizações
|
|
108
|
+
posteriores, a lista permanece visível com `aria-busy`.
|
|
97
109
|
|
|
98
|
-
##
|
|
110
|
+
## Ações em lote
|
|
99
111
|
|
|
100
|
-
`batch`
|
|
112
|
+
`batch` acrescenta seleção à tabela. `can(item)` determina quais linhas podem participar: itens
|
|
113
|
+
inelegíveis não são selecionáveis, “Selecionar todos” inclui somente os elegíveis e `run` recebe
|
|
114
|
+
apenas essa seleção. Com `confirm`, o componente pede confirmação antes de executar. Ao concluir, a
|
|
115
|
+
seleção é limpa e a consulta é refeita.
|
|
101
116
|
|
|
102
117
|
```tsx
|
|
103
118
|
<ActionList action={runList} input={{}}
|
|
@@ -110,9 +125,12 @@ O pattern manda `limit` (= `pageSize`, default 50) e `page` no input; o handler
|
|
|
110
125
|
/>
|
|
111
126
|
```
|
|
112
127
|
|
|
113
|
-
##
|
|
128
|
+
## Outras visualizações
|
|
114
129
|
|
|
115
|
-
|
|
130
|
+
Uma coleção também pode aparecer como quadro, galeria, lista ou calendário. Cada entrada de `views`
|
|
131
|
+
fornece um `render`; `ActionList` preserva a mesma fonte de dados, filtros e estado na URL. Declare
|
|
132
|
+
`icon` para controles somente com ícone; sem ele, o rótulo permanece visível. A visualização
|
|
133
|
+
“Tabela” aparece quando existem colunas.
|
|
116
134
|
|
|
117
135
|
```tsx preview col
|
|
118
136
|
render(
|
|
@@ -146,13 +164,16 @@ render(
|
|
|
146
164
|
)
|
|
147
165
|
```
|
|
148
166
|
|
|
149
|
-
## Filtros dependentes
|
|
167
|
+
## Filtros dependentes e opções remotas
|
|
150
168
|
|
|
151
|
-
|
|
169
|
+
`FilterSpec` também cobre dependências, dicionários e consultas remotas:
|
|
152
170
|
|
|
153
|
-
- **`depends: ['outroFiltro']
|
|
154
|
-
|
|
155
|
-
- **`options: { kind: '
|
|
171
|
+
- **`depends: ['outroFiltro']`:** desabilita o filtro até que suas dependências tenham valor e o
|
|
172
|
+
limpa quando uma delas muda.
|
|
173
|
+
- **`options: { kind: 'dictionary', ref: 'sessionStatus' }`:** resolve opções e rótulos por um
|
|
174
|
+
dicionário registrado no `TbdlibProvider`. `filterOptions` substitui essa fonte quando informado.
|
|
175
|
+
- **`options: { kind: 'lookup', source: 'x.lookup' }`:** consulta uma action com `{ q }` e os valores
|
|
176
|
+
das dependências. A resposta segue o formato `{ value, label }`.
|
|
156
177
|
|
|
157
178
|
```tsx
|
|
158
179
|
filters: {
|
|
@@ -166,9 +187,12 @@ filters: {
|
|
|
166
187
|
}
|
|
167
188
|
```
|
|
168
189
|
|
|
169
|
-
## Exibição e URL
|
|
190
|
+
## Exibição e estado na URL
|
|
170
191
|
|
|
171
|
-
O botão de exibição
|
|
192
|
+
O botão de exibição abre as preferências da listagem em qualquer visualização. Na tabela, permite
|
|
193
|
+
mostrar ou ocultar colunas, inclusive as declaradas com `hidden`; em todas as visualizações, permite
|
|
194
|
+
alterar a quantidade de itens por página. Busca, ordenação, filtros, visualização, colunas e limite
|
|
195
|
+
podem ser serializados na query string pelos helpers do componente:
|
|
172
196
|
|
|
173
197
|
```tsx
|
|
174
198
|
<ActionList
|
|
@@ -181,9 +205,10 @@ O botão de exibição (engrenagem) abre o popover de configuração da listagem
|
|
|
181
205
|
|
|
182
206
|
Convenção compacta (defaults omitidos): `?q=…&sort=chave:dir&status=…&view=board&cols=a,b,c&limit=25`.
|
|
183
207
|
|
|
184
|
-
## Composição
|
|
208
|
+
## Composição dos itens
|
|
185
209
|
|
|
186
|
-
|
|
210
|
+
Passe `children` para substituir a tabela por uma composição própria. A barra de filtros e os
|
|
211
|
+
estados de carregamento, erro e vazio continuam sendo tratados por `ActionList`.
|
|
187
212
|
|
|
188
213
|
```tsx preview col
|
|
189
214
|
render(
|
|
@@ -209,30 +234,30 @@ render(
|
|
|
209
234
|
)
|
|
210
235
|
```
|
|
211
236
|
|
|
212
|
-
##
|
|
237
|
+
## Declaração na ListAction
|
|
213
238
|
|
|
214
239
|
| Chave | O que declara |
|
|
215
240
|
|---|---|
|
|
216
241
|
| `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` fica fora (base do futuro column picker); `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
|
|
217
|
-
| `filters` | `{ [nome]: { label, type, options?, multiple?, advanced?, depends?, … } }` — a toolbar. `advanced` vai
|
|
242
|
+
| `filters` | `{ [nome]: { label, type, options?, multiple?, advanced?, depends?, … } }` — a toolbar. `advanced` vai para o modal; `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
|
|
218
243
|
| `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Com período/filtros no contrato ela fica à direita; sendo a ÚNICA forma de recorte, abre a linha. |
|
|
219
244
|
| `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
|
|
220
245
|
| `periods` | `{ value, label }[]` — o controle de período (presets + Personalizado com calendário); materializa em `from`/`to` no input. |
|
|
221
246
|
|
|
222
|
-
##
|
|
247
|
+
## Propriedades de ActionList
|
|
223
248
|
|
|
224
|
-
|
|
|
249
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
225
250
|
|---|---|---|---|
|
|
226
251
|
| `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item, paginate cursor). |
|
|
227
252
|
| `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
|
|
228
253
|
| `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
|
|
229
|
-
| `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime
|
|
254
|
+
| `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
|
|
230
255
|
| `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
|
|
231
|
-
| `children` | `(items, refetch) => ReactNode` | | Modo
|
|
256
|
+
| `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
|
|
232
257
|
| `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?, destructive? }` — liga a multi-seleção. |
|
|
233
|
-
| `rowId` | `(item) => string` | `item.id` | Identidade da linha
|
|
234
|
-
| `pageSize` | `number` | `50` | Itens por página
|
|
235
|
-
| `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado —
|
|
258
|
+
| `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
|
|
259
|
+
| `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
|
|
260
|
+
| `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
|
|
236
261
|
| `emptyMessage` | `string` | `'Nenhum resultado.'` | O texto do estado vazio. |
|
|
237
|
-
| `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar
|
|
262
|
+
| `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
|
|
238
263
|
| `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita) — apresentação de quem chama, como `cells`; cliques ali não disparam o `onRowClick`. |
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Executar uma action
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Use `ActionTrigger` para executar uma `SimpleAction` a partir de um botão. Sem confirmação, o clique
|
|
4
|
+
inicia a operação, desabilita o controle durante a execução e apresenta as mensagens do contrato em
|
|
5
|
+
um toast. O rótulo padrão vem de `action.label`.
|
|
5
6
|
|
|
6
7
|
```tsx preview
|
|
7
8
|
<DocBrowserActionProvider>
|
|
@@ -11,8 +12,9 @@ Sem confirm, dispara no clique: desabilita enquanto roda e toca o toast do contr
|
|
|
11
12
|
|
|
12
13
|
## Confirmação declarada no contrato
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
Quando a action declara `confirm`, o componente monta o diálogo e aplica o contexto `danger` se a
|
|
16
|
+
operação for destrutiva. Use a propriedade `confirm` somente quando esta ocorrência precisar de uma
|
|
17
|
+
mensagem diferente da declarada no contrato.
|
|
16
18
|
|
|
17
19
|
```tsx preview
|
|
18
20
|
<DocBrowserActionProvider>
|
|
@@ -33,9 +35,11 @@ confirm: {
|
|
|
33
35
|
<ActionTrigger action={sessionDeleteContract} input={{ id: session.id }} />
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
##
|
|
38
|
+
## Ação somente com ícone
|
|
37
39
|
|
|
38
|
-
Com `icon`, o botão
|
|
40
|
+
Com `icon`, o botão exibe somente o ícone e usa `label`, ou `action.label`, no tooltip e no nome
|
|
41
|
+
acessível. O clique não aciona o item clicável ao redor. `itemLabel` identifica o registro na
|
|
42
|
+
mensagem de confirmação.
|
|
39
43
|
|
|
40
44
|
```tsx
|
|
41
45
|
<ActionTrigger
|
|
@@ -47,19 +51,22 @@ Com `icon`, o botão vira icon-only: o `label` (ou o `action.label`) migra pro t
|
|
|
47
51
|
/>
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
>
|
|
54
|
+
> `ActionTrigger` substitui o antigo `DeleteButton`. Ao migrar, declare `confirm` no contrato para
|
|
55
|
+
> preservar a confirmação que o componente anterior sempre apresentava.
|
|
51
56
|
|
|
52
57
|
## Erro que a pessoa entende
|
|
53
58
|
|
|
54
|
-
Quando a action falha,
|
|
59
|
+
Quando a action falha, uma mensagem do servidor substitui o texto genérico somente nos erros de
|
|
60
|
+
negócio `conflict`, `validation` e `not_found`:
|
|
55
61
|
|
|
56
62
|
> Este agente tem 3 conversas — desabilite em vez de excluir.
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
Erros inesperados mantêm a mensagem genérica do contrato, evitando expor detalhes de banco ou
|
|
65
|
+
infraestrutura. O servidor registra o detalhe técnico para diagnóstico.
|
|
59
66
|
|
|
60
|
-
##
|
|
67
|
+
## Propriedades de ActionTrigger
|
|
61
68
|
|
|
62
|
-
|
|
|
69
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
63
70
|
|---|---|---|---|
|
|
64
71
|
| `action` | `SimpleContract<TInput, TData>` | | A SimpleAction do Opus — label, messages e confirm vêm do contrato. |
|
|
65
72
|
| `input` | `TInput` | | O que a action recebe — geralmente { id }. |
|
|
@@ -67,6 +74,6 @@ Isso é acionável. Erro inesperado carrega texto técnico cru: sem a allowlist,
|
|
|
67
74
|
| `variant / size` | `variants do Button` | `'default' (ou 'destructive' se o confirm declarar) / 'default'` | Visual do botão — o destructive do ConfirmSpec já escolhe sozinho. |
|
|
68
75
|
| `confirm` | `{ title, description?, actionLabel?, cancelLabel? }` | | Confirmação via prop — sobrepõe o ConfirmSpec do contrato. |
|
|
69
76
|
| `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
|
|
70
|
-
| `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza
|
|
77
|
+
| `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza para o item. |
|
|
71
78
|
| `itemLabel` | `string` | | Nome do alvo na pergunta (sai entre aspas, em destaque, antes da mensagem do contrato). |
|
|
72
|
-
| `className` | `string` | | Classes do botão (ex.: apertar o tamanho
|
|
79
|
+
| `className` | `string` | | Classes do botão (ex.: apertar o tamanho em uma linha densa). |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Carregar um recurso
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Use `ActionView` para carregar um recurso por uma `ViewAction` e manter carregamento, erro e vazio no
|
|
4
|
+
mesmo fluxo. No exemplo, alterne entre os workspaces para observar o carregamento e a recuperação de
|
|
5
|
+
erro. O consumidor compõe somente o conteúdo disponível.
|
|
6
6
|
|
|
7
7
|
```tsx preview col
|
|
8
8
|
const [id, setId] = useState('empresa-x')
|
|
@@ -35,13 +35,13 @@ render(
|
|
|
35
35
|
)
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Propriedades de ActionView
|
|
39
39
|
|
|
40
|
-
|
|
|
40
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
41
41
|
|---|---|---|---|
|
|
42
42
|
| `action` | `ViewAction<TInput, TData>` | | A ViewAction do Opus (kind view, 1 recurso). |
|
|
43
43
|
| `input` | `TInput` | | Geralmente { id } — mudou, recarrega. |
|
|
44
|
-
| `children` | `(data: TData, refetch) => ReactNode` | |
|
|
45
|
-
| `render` | `(data: TData, refetch) => ReactNode` | | Alias de
|
|
44
|
+
| `children` | `(data: TData, refetch) => ReactNode` | | Conteúdo apresentado quando os dados estão disponíveis. `refetch` permite recarregar por código. |
|
|
45
|
+
| `render` | `(data: TData, refetch) => ReactNode` | | Alias de compatibilidade de `children`; `children` tem precedência. |
|
|
46
46
|
| `loading / empty` | `ReactNode` | `3 skeletons / nada` | Sobrescreve os estados padrão quando o contexto pedir. |
|
|
47
47
|
| `error` | `(err, retry) => ReactNode` | `mensagem + Tentar de novo` | Sobrescreve o estado de erro padrão. |
|
|
@@ -10,7 +10,7 @@ o handler e a web usa a mesma definição para executar ou renderizar a operaç
|
|
|
10
10
|
|
|
11
11
|
## Contrato primeiro
|
|
12
12
|
|
|
13
|
-
> `defineContract` no `shared/` — entidades, schemas (zod + `t.*`
|
|
13
|
+
> `defineContract` no `shared/` — entidades, schemas (zod + `t.*` para tipos lógicos) e os
|
|
14
14
|
> metadados da action. `api` e `web` importam; nada se duplica.
|
|
15
15
|
|
|
16
16
|
```ts
|
|
@@ -64,18 +64,18 @@ name → kind → (label / summary / messages / tags…)
|
|
|
64
64
|
→ invalidates
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
Fail-closed por padrão: `input`/`output` via `z.object` + `t.*`
|
|
67
|
+
Fail-closed por padrão: `input`/`output` via `z.object` + `t.*` para os tipos lógicos; `authorize`
|
|
68
68
|
declarado ou gate explícito do adapter.
|
|
69
69
|
|
|
70
|
-
Os átomos do catálogo servem
|
|
70
|
+
Os átomos do catálogo servem também dentro dos schemas de input/output — não re-escreva
|
|
71
71
|
regex do que o Opus já valida: `z.object({ tag: t.slug().zod(), contato: t.email().zod() })`.
|
|
72
72
|
O `.zod()` devolve o schema zod subjacente do tipo lógico (slug, email, phone, money…).
|
|
73
73
|
|
|
74
74
|
## Modo design — o mock mora no contrato
|
|
75
75
|
|
|
76
76
|
Um `mockHandler` no contrato deixa a UI rodar com dado realista **sem backend nem banco**: em
|
|
77
|
-
`OPUS_MODE=design` o runtime roteia o `execute`
|
|
78
|
-
ausente). Como ele fica no
|
|
77
|
+
`OPUS_MODE=design` o runtime roteia o `execute` para o `mockHandler` (cai no `handler` real se
|
|
78
|
+
ausente). Como ele fica no contrato, e não no `bindAction`, é isomorfo — o design autora, o
|
|
79
79
|
handoff pluga o handler, e o mock nem toca `ctx.db`.
|
|
80
80
|
|
|
81
81
|
Alimente com `fake`/`fakeMany` (fixtures determinísticas do próprio schema — ver [Testes](testing)):
|
|
@@ -92,15 +92,15 @@ export const eventGet = defineContract({
|
|
|
92
92
|
})
|
|
93
93
|
// listas: `mockHandler: () => fakeMany(EventEntity.zod(), 20)`
|
|
94
94
|
|
|
95
|
-
// o handoff, depois, só pluga o real —
|
|
95
|
+
// o handoff, depois, só pluga o real — mesmo contrato:
|
|
96
96
|
export const eventGetImpl = bindAction(eventGet, { handler: async (ctx, input) => { /* db */ } })
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
O `mockHandler` roda no **servidor em modo design** (`OPUS_MODE=design`), não no cliente — e
|
|
100
100
|
quem sobe esse servidor é o próprio dev server: o plugin `opusDesign()` (`@softize/opus/vite`)
|
|
101
101
|
monta o runtime Opus DENTRO do vite e serve `/api` in-process, com os mocks respondendo e
|
|
102
|
-
qualquer `server.proxy`
|
|
103
|
-
503 em envelope — nada vaza
|
|
102
|
+
qualquer `server.proxy` para backend externo desligado (prefixo ex-proxy sem cobertura responde
|
|
103
|
+
503 em envelope — nada vaza para prod). A SPA chama `/api` normal e recebe o dado fake, isolado:
|
|
104
104
|
|
|
105
105
|
```ts
|
|
106
106
|
// vite.config.ts
|
|
@@ -118,7 +118,7 @@ persistindo, o envelope traz a mensagem-guia apontando o entry.
|
|
|
118
118
|
|
|
119
119
|
Como o contrato é isomorfo, o `mockHandler` também acompanha o **bundle web** — peso morto lá
|
|
120
120
|
(a SPA nunca o chama; o dado é fake). Tirá-lo do bundle de prod é um transform de build
|
|
121
|
-
(deferido); **não** dá
|
|
121
|
+
(deferido); **não** dá para gatear no call-site com `import.meta.env` sem quebrar o servidor
|
|
122
122
|
(não existe em Node). É a materialização Opus-nativa do "mock = contrato": o design não é
|
|
123
123
|
descartável — vira o contrato que o backend honra.
|
|
124
124
|
|
|
@@ -64,10 +64,10 @@ handler: async (ctx, input) => {
|
|
|
64
64
|
Marque uma action com `ai: { enabled: true }` no contrato e ela vira uma **tool** que o
|
|
65
65
|
modelo pode chamar. Aí `ctx.ai.run(prompt)` roda o loop agêntico sobre as actions `ai:enabled`:
|
|
66
66
|
o modelo escolhe a tool → o runtime executa a action **como o usuário logado** (limitado pelo
|
|
67
|
-
`ctx.can`) → o resultado volta
|
|
67
|
+
`ctx.can`) → o resultado volta para o modelo → repete até a resposta em texto.
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
|
-
// A action opta por entrar
|
|
70
|
+
// A action opta por entrar; o schema do contrato também descreve a ferramenta:
|
|
71
71
|
export const buscarNotas = defineContract({
|
|
72
72
|
name: 'nota.buscar',
|
|
73
73
|
kind: 'list',
|
|
@@ -76,7 +76,7 @@ export const buscarNotas = defineContract({
|
|
|
76
76
|
ai: { enabled: true, description: 'Busca notas por cliente e mês.' },
|
|
77
77
|
})
|
|
78
78
|
|
|
79
|
-
// No handler — ou fora dele, via runtime.aiFor(base),
|
|
79
|
+
// No handler — ou fora dele, via runtime.aiFor(base), para o backend de um chat:
|
|
80
80
|
const { text } = await ctx.ai!.run('Quantas notas a Empresa X emitiu em junho?')
|
|
81
81
|
// o modelo chamou nota.buscar sozinho, como o usuário logado, e respondeu em texto.
|
|
82
82
|
```
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
## Forma curta
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use a forma curta quando o aviso tiver ícone, título e uma frase. Declare `title`, `description` e
|
|
4
|
+
`icon`; o componente monta os slots e anuncia o conteúdo com `role="alert"`.
|
|
4
5
|
|
|
5
6
|
```tsx preview col
|
|
6
7
|
<Alert icon={<Info />} title="Opus 2.8.0" description="Este workspace usa a versão pinada em opus.json." />
|
|
@@ -37,10 +38,9 @@ borda e texto; o conteúdo comunica o significado sem depender somente da cor.
|
|
|
37
38
|
## Mídia é opcional
|
|
38
39
|
|
|
39
40
|
Sem `icon` o alert mantém somente a coluna de texto. Com ele, a forma curta materializa
|
|
40
|
-
`AlertMedia` à esquerda e `AlertHeader` à direita. A mídia
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
uma descrição.
|
|
41
|
+
`AlertMedia` à esquerda e `AlertHeader` à direita. A mídia mantém uma moldura quadrada de tamanho
|
|
42
|
+
estável, mesmo quando o título ou a descrição ocupam mais linhas. Texto solto como filho também
|
|
43
|
+
vale (`<Alert>Sincronizado.</Alert>`) e se torna uma descrição.
|
|
44
44
|
|
|
45
45
|
```tsx preview col
|
|
46
46
|
<Alert title="Sem provider próprio" description="As conversas usam o padrão do sistema." />
|
|
@@ -51,7 +51,8 @@ uma descrição.
|
|
|
51
51
|
|
|
52
52
|
Quando a mensagem precisa de conteúdo rico, componha os slots da família. `AlertMedia`,
|
|
53
53
|
`AlertHeader` e `AlertActions` são filhos diretos de `Alert`; `AlertTitle` e
|
|
54
|
-
`AlertDescription` pertencem ao header.
|
|
54
|
+
`AlertDescription` pertencem ao header. As ações ficam no fim lógico da superfície e centralizadas
|
|
55
|
+
verticalmente na mesma linha do conteúdo — a mesma posição usada pelas ações do Toast.
|
|
55
56
|
|
|
56
57
|
```tsx preview col
|
|
57
58
|
<Alert context="danger">
|
|
@@ -59,21 +60,22 @@ Quando a mensagem precisa de conteúdo rico, componha os slots da família. `Ale
|
|
|
59
60
|
<CircleAlert />
|
|
60
61
|
</AlertMedia>
|
|
61
62
|
<AlertHeader>
|
|
62
|
-
<AlertTitle>Não deu
|
|
63
|
+
<AlertTitle>Não deu para publicar</AlertTitle>
|
|
63
64
|
<AlertDescription>
|
|
64
65
|
<p>O registry recusou a versão 3.0.0 — ela já existe.</p>
|
|
65
66
|
<p>Suba o patch e tente de novo.</p>
|
|
66
67
|
</AlertDescription>
|
|
67
68
|
</AlertHeader>
|
|
68
69
|
<AlertActions>
|
|
69
|
-
<Button size="sm"
|
|
70
|
+
<Button size="sm">
|
|
70
71
|
Tentar novamente
|
|
71
72
|
</Button>
|
|
72
73
|
</AlertActions>
|
|
73
74
|
</Alert>
|
|
74
75
|
```
|
|
75
76
|
|
|
76
|
-
Os dois modos convivem: com `title
|
|
77
|
+
Os dois modos convivem: com `title` ou `description`, `children` entra depois da mensagem e recebe a
|
|
78
|
+
ação.
|
|
77
79
|
|
|
78
80
|
```tsx preview col
|
|
79
81
|
<Alert
|
|
@@ -81,15 +83,15 @@ Os dois modos convivem: com `title`/`description` preenchidos, `children` entra
|
|
|
81
83
|
title="Sessão presa"
|
|
82
84
|
description="O ambiente não subiu no tempo esperado."
|
|
83
85
|
>
|
|
84
|
-
<Button size="sm"
|
|
86
|
+
<Button size="sm">
|
|
85
87
|
Reiniciar
|
|
86
88
|
</Button>
|
|
87
89
|
</Alert>
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
##
|
|
92
|
+
## Propriedades de Alert
|
|
91
93
|
|
|
92
|
-
|
|
|
94
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
93
95
|
| ------------- | ----------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
94
96
|
| `title` | `React.ReactNode` | | Título do alert (a forma curta). Não é o atributo `title` do HTML — esse é tooltip nativo, banido na casa, e o componente não o aceita. |
|
|
95
97
|
| `description` | `React.ReactNode` | | A frase. Sozinha, dispensa título. |
|
|
@@ -15,7 +15,7 @@ contêiner (o pai): a altura o componente deriva sozinho.
|
|
|
15
15
|
|
|
16
16
|
## Quadrado (1/1)
|
|
17
17
|
|
|
18
|
-
ratio={1} trava
|
|
18
|
+
ratio={1} trava em um quadrado — o formato dos avatares de agente e dos ícones de skill, onde a
|
|
19
19
|
moldura precisa ser previsível.
|
|
20
20
|
|
|
21
21
|
```tsx preview col-start
|
|
@@ -57,10 +57,10 @@ layout quando o preview carrega.
|
|
|
57
57
|
</div>
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
##
|
|
60
|
+
## Propriedades de AspectRatio
|
|
61
61
|
|
|
62
|
-
|
|
|
62
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
63
63
|
|---|---|---|---|
|
|
64
|
-
| `ratio` | `number` | `1` | A razão largura/altura. 16/9
|
|
64
|
+
| `ratio` | `number` | `1` | A razão largura/altura. 16/9 para vídeo/preview, 1 para quadrado, 4/3 para clássico. |
|
|
65
65
|
| `children` | `React.ReactNode` | | O conteúdo a enquadrar (img, iframe, div) — preencha com h-full w-full e object-cover. |
|
|
66
66
|
| `className` | `string` | | Estilo do bloco — borda, rounded e overflow-hidden moram aqui, não no filho. |
|
|
@@ -5,7 +5,7 @@ title: Auditoria
|
|
|
5
5
|
# Auditoria
|
|
6
6
|
|
|
7
7
|
Toda action executada vira um registro imutável: quem, o quê, quando, com que entrada e
|
|
8
|
-
resultado. O runtime emite um `AuditRecord` por execução
|
|
8
|
+
resultado. O runtime emite um `AuditRecord` por execução para o(s) sink(s) configurado(s) — de
|
|
9
9
|
graça, sem o handler pedir. O contrato é o `AuditSink`.
|
|
10
10
|
|
|
11
11
|
## O contrato
|
|
@@ -43,7 +43,7 @@ interface AuditRecord {
|
|
|
43
43
|
import { consoleAudit } from '@softize/opus/audit/console'
|
|
44
44
|
import { pgAudit } from '@softize/opus/audit/pg'
|
|
45
45
|
|
|
46
|
-
// Dev: uma linha por action (pretty
|
|
46
|
+
// Dev: uma linha por action (pretty em um TTY, json senão).
|
|
47
47
|
const dev = consoleAudit()
|
|
48
48
|
|
|
49
49
|
// Produção: grava na tabela audit_log (Postgres).
|
|
@@ -7,7 +7,7 @@ title: Autenticação
|
|
|
7
7
|
Quem é o usuário, de qual tenant, e o que ele pode. O contrato é um adapter do core
|
|
8
8
|
(`AuthAdapter`): a cada request o runtime chama `resolveContext` e injeta o resultado no
|
|
9
9
|
handler — `ctx.user`, `ctx.tenantId`, `ctx.can`. O Opus **não** implementa RBAC/ABAC; ele
|
|
10
|
-
delega a decisão
|
|
10
|
+
delega a decisão para o `can` que o driver pluga.
|
|
11
11
|
|
|
12
12
|
## O contrato
|
|
13
13
|
|
|
@@ -31,7 +31,7 @@ política usada por ela.
|
|
|
31
31
|
import { jwtAuth } from '@softize/opus/auth/jwt'
|
|
32
32
|
import { betterAuthSession } from '@softize/opus/auth/better-auth'
|
|
33
33
|
|
|
34
|
-
// JWT: valida o token (HS256 por padrão) e mapeia o payload
|
|
34
|
+
// JWT: valida o token (HS256 por padrão) e mapeia o payload para o User.
|
|
35
35
|
const jwt = jwtAuth({ secret: process.env.JWT_SECRET! })
|
|
36
36
|
|
|
37
37
|
// better-auth: valida a sessão contra um IdP better-auth remoto.
|
|
@@ -44,7 +44,7 @@ const idp = betterAuthSession({
|
|
|
44
44
|
|
|
45
45
|
O `jwt` procura o token no `Authorization: Bearer`, em cookie ou custom (via `getToken`); o
|
|
46
46
|
`secret` pode ser string ou um resolver async por `kid`. O `betterAuthSession` faz fetch da
|
|
47
|
-
sessão no IdP (`timeoutMs`, default 5s) e mapeia
|
|
47
|
+
sessão no IdP (`timeoutMs`, default 5s) e mapeia para o `User`/tenant/can (o `can` default nega
|
|
48
48
|
tudo — plugue o seu).
|
|
49
49
|
|
|
50
50
|
## No runtime
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Retrato com imagem
|
|
2
2
|
|
|
3
3
|
AvatarImage com src/alt e AvatarFallback com as iniciais — o fallback aparece enquanto a imagem
|
|
4
4
|
carrega ou se ela falha.
|
|
@@ -12,8 +12,8 @@ carrega ou se ela falha.
|
|
|
12
12
|
|
|
13
13
|
## Fallback de iniciais
|
|
14
14
|
|
|
15
|
-
Sem AvatarImage (ou com src quebrado), só o AvatarFallback renderiza — iniciais
|
|
16
|
-
|
|
15
|
+
Sem AvatarImage (ou com src quebrado), só o AvatarFallback renderiza — iniciais para pessoa, ícone
|
|
16
|
+
para agente.
|
|
17
17
|
|
|
18
18
|
```tsx preview
|
|
19
19
|
<Avatar>
|
|
@@ -28,7 +28,7 @@ pra agente.
|
|
|
28
28
|
|
|
29
29
|
## Tamanhos
|
|
30
30
|
|
|
31
|
-
size sm/default/lg — o fallback acompanha o tamanho. sm
|
|
31
|
+
size sm/default/lg — o fallback acompanha o tamanho. sm para listas densas, lg para cabeçalho de
|
|
32
32
|
workspace.
|
|
33
33
|
|
|
34
34
|
```tsx preview
|
|
@@ -45,8 +45,8 @@ workspace.
|
|
|
45
45
|
|
|
46
46
|
## Com selo de status
|
|
47
47
|
|
|
48
|
-
AvatarBadge é o ponto no canto inferior — verde
|
|
49
|
-
|
|
48
|
+
AvatarBadge é o ponto no canto inferior — verde para o agente developer rodando uma sessão, cinza
|
|
49
|
+
para o ocioso. Tinja com className.
|
|
50
50
|
|
|
51
51
|
```tsx preview
|
|
52
52
|
<Avatar>
|
|
@@ -82,13 +82,33 @@ excedente — os membros do workspace Empresa X.
|
|
|
82
82
|
</AvatarGroup>
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
##
|
|
85
|
+
## Propriedades de Avatar
|
|
86
86
|
|
|
87
|
-
|
|
|
87
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
88
88
|
|---|---|---|---|
|
|
89
|
-
| `size`
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
|
94
|
-
|
|
89
|
+
| `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | Diâmetro do retrato; o fallback e o selo acompanham a escala. |
|
|
90
|
+
|
|
91
|
+
## Propriedades de AvatarImage
|
|
92
|
+
|
|
93
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| `src` | `string` | | URL da imagem. Enquanto ela carrega ou quando falha, `AvatarFallback` ocupa o lugar. |
|
|
96
|
+
| `alt` | `string` | | Texto alternativo que identifica a pessoa ou o agente. |
|
|
97
|
+
|
|
98
|
+
## Propriedades de AvatarFallback
|
|
99
|
+
|
|
100
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| `children` | `React.ReactNode` | | Iniciais ou ícone exibido quando a imagem não está disponível. |
|
|
103
|
+
|
|
104
|
+
## Propriedades de AvatarBadge
|
|
105
|
+
|
|
106
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| `className` | `string` | | Classes usadas para comunicar visualmente o status no selo. |
|
|
109
|
+
|
|
110
|
+
## Propriedades de AvatarGroupCount
|
|
111
|
+
|
|
112
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| `children` | `React.ReactNode` | | Quantidade excedente no fim do grupo, como `+3`. |
|