@softize/opus 19.0.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 +193 -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 +5 -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 +15 -3
- package/src/ui/components/patterns/fill-region.ts +150 -0
- package/src/ui/components/patterns/list.tsx +89 -78
- package/src/ui/components/patterns/page.tsx +20 -4
- package/src/ui/components/patterns/presentation.tsx +52 -37
- package/src/ui/components/patterns/split.tsx +44 -4
- package/src/ui/components/patterns/surface-anatomy.ts +35 -8
- 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/carousel.tsx +34 -5
- package/src/ui/components/primitives/checkbox.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +2 -1
- package/src/ui/components/primitives/copyable.tsx +120 -10
- package/src/ui/components/primitives/dialog.tsx +41 -10
- package/src/ui/components/primitives/drawer.tsx +9 -1
- 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/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 +41 -15
- 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 +31 -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 +107 -24
- 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 +63 -19
- 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 +12 -4
- package/src/ui/docs/content/observability.md +10 -9
- package/src/ui/docs/content/page.md +118 -12
- package/src/ui/docs/content/pagination.md +13 -5
- package/src/ui/docs/content/pane.md +55 -5
- 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 +3 -2
- package/src/ui/docs/content/skeleton.md +6 -1
- 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 +19 -0
- 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 +6 -0
- package/src/ui/drivers/react.tsx +16 -8
- package/src/ui/meta.ts +31 -31
- package/src/vite/design.ts +16 -4
|
@@ -10,12 +10,13 @@ superfície, e a escolha entre regiões seccionadas e contínuas pertence a quem
|
|
|
10
10
|
Quando a própria região precisar de uma fronteira visível, use `framed`, descrito adiante. Pelo
|
|
11
11
|
mesmo motivo o `Content` não tem rodapé: um rodapé pertence à superfície que a hospeda.
|
|
12
12
|
|
|
13
|
-
No caso comum, prefira o shorthand:
|
|
13
|
+
No caso comum, prefira o shorthand. `count` mostra o total de itens ao lado do título:
|
|
14
14
|
|
|
15
15
|
```tsx preview col
|
|
16
16
|
render(
|
|
17
17
|
<Content
|
|
18
18
|
title="Dispositivos conectados"
|
|
19
|
+
count={3}
|
|
19
20
|
description="Sessões com acesso à sua conta."
|
|
20
21
|
actions={<Button variant="outline">Encerrar outras sessões</Button>}
|
|
21
22
|
>
|
|
@@ -132,13 +133,33 @@ do pai correto, filhos estruturais indiretos e a mistura de shorthand com compos
|
|
|
132
133
|
## Coleção nomeada
|
|
133
134
|
|
|
134
135
|
Quando a região principal da página é uma coleção, `Content` nomeia e governa essa coleção, enquanto
|
|
135
|
-
`ActionList` executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
|
|
136
|
+
[`ActionList`](/ui/action-list) executa a consulta. Coloque criar, importar e outras ações sobre o conjunto completo em
|
|
136
137
|
`ContentActions`. Busca, filtros, atualização e operações dependentes do recorte atual permanecem na
|
|
137
138
|
toolbar da lista. Essa divisão aproxima cada comando do objeto que ele afeta sem criar uma família
|
|
138
|
-
paralela de componentes `List*`.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
paralela de componentes `List*`.
|
|
140
|
+
|
|
141
|
+
Quando a coleção é o assunto da página, use `variant="page"` com `level={1}`: o `Content` assume a
|
|
142
|
+
escala do cabeçalho de [`Page`](/ui/page) e passa a ser o heading principal. As ações ficam no
|
|
143
|
+
extremo oposto ao título, como em qualquer variante.
|
|
144
|
+
|
|
145
|
+
```tsx preview col
|
|
146
|
+
render(
|
|
147
|
+
<Content level={1} variant="page" title="Clientes" actions={<Button>Criar cliente</Button>}>
|
|
148
|
+
<div className="rounded-lg border border-border p-4 text-sm text-muted-foreground">
|
|
149
|
+
A lista de clientes ocupa o corpo.
|
|
150
|
+
</div>
|
|
151
|
+
</Content>,
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A criação usa um `Button` textual na variante padrão (`solid`) e no tamanho `default`, sem ícone,
|
|
156
|
+
nomeado `Criar {recurso}` (por exemplo, `Criar cliente`); o diálogo aberto pelo gatilho repete esse
|
|
157
|
+
título, e a edição usa `Editar {recurso}`.
|
|
158
|
+
|
|
159
|
+
Em uma [`Page`](/ui/page) com `layout="fill"`, a coleção pode ocupar a altura da página e rolar só os
|
|
160
|
+
resultados. `Content` e `ContentBody` repassam a altura até a [`ActionList`](/ui/action-list) sem
|
|
161
|
+
classes adicionais; um `Content` irmão, sem lista, mantém a altura do próprio conteúdo. A seção
|
|
162
|
+
“Ocupar a altura disponível” da `ActionList` mostra a composição completa.
|
|
142
163
|
|
|
143
164
|
## Propriedades de Content
|
|
144
165
|
|
|
@@ -147,12 +168,15 @@ gatilho repete esse título e a edição usa `Editar recurso`.
|
|
|
147
168
|
| `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
|
|
148
169
|
| `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
|
|
149
170
|
| `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
|
|
150
|
-
| `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira
|
|
171
|
+
| `actions` | `ReactNode` | | Forma curta: ações sobre a região inteira, no extremo oposto ao título. |
|
|
151
172
|
| `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | `2` | Nível semântico do heading, independente do destaque visual. |
|
|
152
173
|
| `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
|
|
153
174
|
| `as` | `'section' \| 'fieldset'` | `'section'` | Usa uma região com heading ou um grupo de campos cujo título é a legenda nativa. |
|
|
154
175
|
| `framed` | `boolean` | `false` | Envolve o header e o body com a moldura estrutural da região. |
|
|
155
176
|
| `frameVariant` | `'default' \| 'legend'` | `'default'` | Em `fieldset` com moldura, mantém a borda contínua ou integra visualmente a legenda à borda. Não altera a semântica. |
|
|
177
|
+
| `children` | `ReactNode` | | Corpo da região na forma curta, ou `ContentHeader` e `ContentBody` na composição explícita. |
|
|
178
|
+
|
|
179
|
+
As demais propriedades são as nativas de `section` ou, com `as="fieldset"`, de `fieldset`.
|
|
156
180
|
|
|
157
181
|
Na composição explícita, `ContentHeader` recebe `ContentTitle`, `ContentMeta`, `ContentDescription` e
|
|
158
182
|
`ContentActions`, e `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level`
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
## Copiar por ícone
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Copyable` para que a pessoa copie um valor exato com um clique, como um identificador, uma
|
|
4
|
+
chave ou um endereço. Sem filhos, ele é um botão só com ícone: copia `value` e troca o ícone por um
|
|
5
|
+
check durante 1,5 segundo. Essa forma cabe em barras de ferramentas e células estreitas, ao lado do
|
|
6
|
+
valor exibido.
|
|
4
7
|
|
|
5
8
|
```tsx preview
|
|
6
9
|
<Copyable value="opus_sk_1a2b3c4d5e6f" className="text-muted-foreground hover:text-foreground" />
|
|
@@ -8,7 +11,8 @@ Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5
|
|
|
8
11
|
|
|
9
12
|
## Com rótulo
|
|
10
13
|
|
|
11
|
-
Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar no conjunto copia.
|
|
14
|
+
Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar no conjunto copia. Borda,
|
|
15
|
+
fonte monoespaçada e demais detalhes visuais entram por `className`.
|
|
12
16
|
|
|
13
17
|
```tsx preview
|
|
14
18
|
<Copyable
|
|
@@ -19,21 +23,48 @@ Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar n
|
|
|
19
23
|
</Copyable>
|
|
20
24
|
```
|
|
21
25
|
|
|
26
|
+
## Nome acessível
|
|
27
|
+
|
|
28
|
+
Com rótulo visível, o nome do botão é o próprio texto dos filhos. Quem usa comando de voz aciona o
|
|
29
|
+
botão dizendo o que vê, e o nome não muda durante a confirmação. Sem filhos, o botão se chama
|
|
30
|
+
“Copiar” e, durante a confirmação, “Copiado”.
|
|
31
|
+
|
|
32
|
+
A confirmação também é anunciada por leitores de tela: uma região de status, invisível na tela e
|
|
33
|
+
fora do botão, diz “Copiado” sem interromper a leitura em andamento.
|
|
34
|
+
|
|
35
|
+
Quando houver mais de um botão só com ícone na mesma tela, passe `aria-label` para distinguir o que
|
|
36
|
+
cada um copia, como “Copiar o ID do pedido”. Com rótulo visível, o `aria-label` raramente é
|
|
37
|
+
necessário; se usá-lo, comece pelo texto que a pessoa vê. Um `aria-label` próprio substitui os nomes
|
|
38
|
+
padrão, inclusive o “Copiado”, e o anúncio da confirmação continua.
|
|
39
|
+
|
|
22
40
|
## Duração do feedback
|
|
23
41
|
|
|
24
|
-
`feedbackMs` ajusta por quanto tempo a confirmação aparece; o padrão é 1500 milissegundos.
|
|
25
|
-
área de transferência não estiver disponível, a ação não produz efeito.
|
|
42
|
+
`feedbackMs` ajusta por quanto tempo a confirmação aparece; o padrão é 1500 milissegundos.
|
|
26
43
|
|
|
27
44
|
```tsx preview
|
|
28
|
-
<Copyable
|
|
29
|
-
|
|
45
|
+
<Copyable
|
|
46
|
+
value="https://opus.softize.dev/w/empresa-x"
|
|
47
|
+
feedbackMs={3000}
|
|
48
|
+
className="text-muted-foreground hover:text-foreground"
|
|
49
|
+
>
|
|
50
|
+
Copiar link
|
|
30
51
|
</Copyable>
|
|
31
52
|
```
|
|
32
53
|
|
|
54
|
+
A confirmação só aparece depois que a área de transferência aceita o texto. Se ela não estiver
|
|
55
|
+
disponível, como em uma página sem HTTPS, ou recusar a escrita, o ícone não muda.
|
|
56
|
+
|
|
33
57
|
## Propriedades de Copyable
|
|
34
58
|
|
|
35
59
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
36
60
|
|---|---|---|---|
|
|
37
|
-
| `value` | `string` | | O texto que vai para a área de transferência no clique. |
|
|
61
|
+
| `value` | `string` | obrigatório | O texto que vai para a área de transferência no clique. |
|
|
38
62
|
| `feedbackMs` | `number` | `1500` | Duração do estado “copiado”, em milissegundos. |
|
|
39
|
-
| `children` | `ReactNode` | | Rótulo ao lado do ícone
|
|
63
|
+
| `children` | `ReactNode` | | Rótulo visível ao lado do ícone. Quando presente, é o nome acessível do botão. |
|
|
64
|
+
| `aria-label` | `string` | sem filhos, `'Copiar'`, ou `'Copiado'` durante a confirmação | Nome acessível do botão. Substitui o nome vindo dos filhos e os nomes padrão. |
|
|
65
|
+
|
|
66
|
+
Os demais atributos vão para o elemento `button`. Um `onClick` próprio roda antes da cópia. O
|
|
67
|
+
botão é o único elemento que o Copyable coloca no contêiner: a região de status fica no fim do
|
|
68
|
+
`body`, visível apenas para leitores de tela, e é uma só para todos os Copyable da página. Ela
|
|
69
|
+
aparece com o primeiro Copyable montado e sai com o último, então uma tabela com um botão de copiar
|
|
70
|
+
por linha não multiplica regiões.
|
|
@@ -4,15 +4,16 @@ title: Customização
|
|
|
4
4
|
|
|
5
5
|
# Customização
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Quando a aplicação precisa de outra marca, outra largura ou outro comportamento, o Opus oferece
|
|
8
|
+
cinco alavancas, da mais global à mais pontual. Todas partem de compor e configurar os componentes
|
|
9
|
+
existentes; nenhuma exige copiar um componente para dentro do projeto e mantê-lo à parte. O que não
|
|
10
|
+
couber nelas evolui no próprio Opus, para valer em todos os projetos.
|
|
10
11
|
|
|
11
12
|
## 1 · Identidade por tokens
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Para aplicar a marca, sobrescreva as variáveis CSS em `:root` depois de importar `theme.css`. Todos
|
|
15
|
+
os componentes acompanham a mudança, e o tema escuro também: a classe `.dark` redeclara as mesmas
|
|
16
|
+
variáveis.
|
|
16
17
|
|
|
17
18
|
```css
|
|
18
19
|
/* index.css do app — depois do import do tema. */
|
|
@@ -26,27 +27,20 @@ divergência declarada), para valer para casa toda.
|
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
O Opus deriva de `rounded-xs` a `rounded-2xl` dessa base. Assim, a marca muda a presença dos
|
|
29
|
-
cantos sem substituir as utilities escolhidas pelos componentes nem criar tokens por papel.
|
|
30
|
+
cantos sem substituir as utilities escolhidas pelos componentes nem criar tokens por papel. A lista
|
|
31
|
+
completa de variáveis está em [Tokens](/ui/tokens).
|
|
30
32
|
|
|
31
33
|
O tema não define `font-size` em `html`: a fonte raiz pertence ao navegador e à aplicação.
|
|
32
34
|
Quando o produto precisa de outra densidade, declare essa escolha no CSS do app. Por exemplo,
|
|
33
|
-
`html { font-size: 93.75%; }`
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
## Layout desktop
|
|
37
|
-
|
|
38
|
-
O Opus oferece suporte a partir de `64rem` e não muda a composição conforme a largura da viewport.
|
|
39
|
-
Classes de tamanho, como `size="sm"`, escolhem a densidade do componente; não representam
|
|
40
|
-
breakpoints. Overflow e limites contra a janela continuam protegendo o conteúdo.
|
|
41
|
-
|
|
42
|
-
Não adicione uma adaptação isolada por viewport ou container em `className`. Uma futura
|
|
43
|
-
responsividade precisa começar pelo shell e formar um contrato comum para navegação, splits,
|
|
44
|
-
tabelas e componentes.
|
|
35
|
+
`html { font-size: 93.75%; }` reduz a raiz a 15/16 da fonte que o navegador oferece, e tudo que o
|
|
36
|
+
Opus mede em `rem` acompanha a proporção. O valor continua sendo uma escolha do app, não uma regra
|
|
37
|
+
da biblioteca.
|
|
45
38
|
|
|
46
39
|
## 2 · className em tudo
|
|
47
40
|
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
Todo componente aceita `className` e o combina com as próprias classes por tailwind-merge: quando
|
|
42
|
+
duas classes disputam a mesma propriedade, a do consumidor vence. Use essa alavanca para o layout
|
|
43
|
+
local, como largura, margem e grid, e não para repintar o visual dos componentes.
|
|
50
44
|
|
|
51
45
|
```tsx
|
|
52
46
|
<Button className="w-full">Continuar</Button>
|
|
@@ -56,73 +50,88 @@ tabelas e componentes.
|
|
|
56
50
|
|
|
57
51
|
## 3 · Recomposição estrutural
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
Os componentes são divididos em partes nomeadas, como `CardHeader` e `CardBody`, e a tela usa só as
|
|
54
|
+
que precisa. `asChild` faz o filho assumir o estilo e o comportamento do componente, e as funções
|
|
55
|
+
`*Variants` aplicam o visual de um componente a um elemento qualquer.
|
|
61
56
|
|
|
62
57
|
```tsx preview
|
|
63
58
|
<Button asChild variant="outline">
|
|
64
|
-
<a href="#" onClick={(e) => e.preventDefault()}
|
|
59
|
+
<a href="#" onClick={(e) => e.preventDefault()}>Link com aparência de botão</a>
|
|
65
60
|
</Button>
|
|
66
61
|
```
|
|
67
62
|
|
|
68
63
|
```tsx
|
|
69
|
-
//
|
|
64
|
+
// Partes: só o que a tela pede.
|
|
70
65
|
<Card>
|
|
71
66
|
<CardHeader><CardTitle>Workspace</CardTitle></CardHeader>
|
|
72
67
|
<CardBody>…</CardBody>
|
|
73
68
|
</Card>
|
|
74
69
|
|
|
75
|
-
// asChild: o
|
|
70
|
+
// asChild: o link assume o estilo e o comportamento do botão.
|
|
76
71
|
<Button asChild><a href="/docs">Abrir documentação</a></Button>
|
|
77
72
|
|
|
78
|
-
// buttonVariants:
|
|
73
|
+
// buttonVariants: o visual do botão em outro elemento.
|
|
79
74
|
<a className={buttonVariants({ variant: 'outline' })}>Link estilizado</a>
|
|
80
75
|
```
|
|
81
76
|
|
|
82
77
|
## 4 · Comportamento por props
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
Tudo o que abre ou seleciona aceita modo controlado (`open` e `onOpenChange`, `value` e
|
|
80
|
+
`onValueChange`). Posicionamento (`align`, `side`, `sideOffset`) e tempos (`delayDuration`) também
|
|
81
|
+
são props. A orquestração fica com a aplicação; o visual continua o dos componentes.
|
|
87
82
|
|
|
88
83
|
```tsx
|
|
89
84
|
const [open, setOpen] = useState(false)
|
|
90
85
|
|
|
91
86
|
<Dialog open={open} onOpenChange={setOpen}>…</Dialog>
|
|
92
|
-
<
|
|
87
|
+
<Dialog mode="alert">…</Dialog> {/* exige uma resposta explícita */}
|
|
93
88
|
<PopoverContent align="start" sideOffset={8} />
|
|
94
89
|
<TooltipProvider delayDuration={200}>…
|
|
95
90
|
```
|
|
96
91
|
|
|
92
|
+
Para exigir uma resposta, use `Dialog mode="alert"`: ele não fecha pelo clique externo e não mostra
|
|
93
|
+
o botão de fechar. Apenas esconder o botão com `showCloseButton={false}` não impede o fechamento por
|
|
94
|
+
Esc nem pelo clique externo. Veja [Dialog](/ui/dialog).
|
|
95
|
+
|
|
97
96
|
## 5 · Nos patterns, o contrato é a customização
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
Nos patterns — [`ActionForm`](/ui/action-form), [`ActionList`](/ui/action-list),
|
|
99
|
+
[`ActionView`](/ui/action-view) e [`ActionTrigger`](/ui/action-trigger) —, o contrato da action
|
|
100
|
+
determina campos, colunas e estados. A tela ajusta o que é dela: `fieldOptions` no formulário,
|
|
101
|
+
`columns` na lista, a função de render no `ActionView`. Para ir além, envolva o pattern em um
|
|
102
|
+
componente da feature em vez de copiá-lo.
|
|
102
103
|
|
|
103
104
|
```tsx
|
|
104
|
-
// O
|
|
105
|
+
// O formulário vem do contrato; a tela só fornece as opções que conhece.
|
|
105
106
|
<ActionForm
|
|
106
107
|
action={workspaceCreateContract}
|
|
107
|
-
fieldOptions={{
|
|
108
|
+
fieldOptions={{ ownerId: workspaceMembers }}
|
|
108
109
|
onSuccess={(d) => navigate(`/workspaces/${d.id}`)}
|
|
109
110
|
/>
|
|
110
111
|
|
|
111
|
-
//
|
|
112
|
+
// A lista recebe as colunas; a célula é render livre.
|
|
112
113
|
<ActionList action={listAction} input={{ q }} columns={[
|
|
113
114
|
{ key: 'name', header: 'Workspace', cell: (w) => w.name },
|
|
114
115
|
]} />
|
|
115
116
|
```
|
|
116
117
|
|
|
117
|
-
##
|
|
118
|
+
## Largura mínima e densidade
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
O Opus oferece suporte a partir de `64rem` e não muda a composição conforme a largura da viewport.
|
|
121
|
+
Props de tamanho, como `size="sm"`, escolhem a densidade do componente; não representam
|
|
122
|
+
breakpoints. Overflow e limites contra a janela continuam protegendo o conteúdo.
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
Não adicione em `className` uma adaptação isolada por viewport ou container. Uma responsividade
|
|
125
|
+
futura precisa começar pelo shell e formar um contrato comum para navegação, splits, tabelas e
|
|
126
|
+
componentes.
|
|
127
|
+
|
|
128
|
+
## O que fica fora das alavancas
|
|
129
|
+
|
|
130
|
+
Alguns detalhes fazem parte da identidade visual do Opus e são iguais em todo projeto: a faixa do
|
|
131
|
+
rodapé do dialog, a elevação no tema escuro, o escurecimento do botão ao ser pressionado
|
|
132
|
+
(`active:brightness-90`) e a seta do tooltip. Eles não são configuráveis pela aplicação.
|
|
125
133
|
|
|
126
|
-
Se uma necessidade real não cabe nas alavancas (tokens
|
|
127
|
-
contrato),
|
|
128
|
-
declarada
|
|
134
|
+
Se uma necessidade real não cabe nas alavancas (tokens, `className`, partes e `asChild`, props e
|
|
135
|
+
contrato), não crie uma variação local. Proponha a evolução do componente no Opus, com a
|
|
136
|
+
divergência declarada conforme a skill `build-opus-ui`; assim a mudança vale para todos os projetos
|
|
137
|
+
e passa a aparecer nesta documentação.
|
|
@@ -1,36 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Evolução contínua
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Evolução contínua
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
Use este fluxo quando o seu projeto precisar de algo que o Opus ainda não oferece ou encontrar um
|
|
8
|
+
defeito nele. O Opus incorpora um comportamento quando ele se repete em mais de um projeto; então o
|
|
9
|
+
projeto troca a implementação local pelo import. Preferência de estilo e conveniência isolada não
|
|
10
|
+
são motivo para incorporar uma peça.
|
|
4
11
|
|
|
5
|
-
##
|
|
12
|
+
## Registrar um apontamento
|
|
6
13
|
|
|
7
|
-
Cada apontamento vira uma Issue
|
|
14
|
+
Cada apontamento vira uma Issue em
|
|
15
|
+
[github.com/softize-dev/opus](https://github.com/softize-dev/opus/issues). Antes de abrir uma nova,
|
|
16
|
+
procure uma Issue sobre o mesmo comportamento e comente nela. Há dois tipos:
|
|
8
17
|
|
|
9
|
-
- **`enhancement
|
|
10
|
-
|
|
18
|
+
- **`enhancement`:** código genérico que serviria a qualquer projeto. Antes de apontar, confira se o
|
|
19
|
+
Opus já oferece o recurso no inventário de componentes, nos exports ou nesta documentação; se
|
|
20
|
+
oferecer, use-o.
|
|
21
|
+
- **`bug`:** defeito ou limitação do próprio Opus, encontrado durante o uso.
|
|
11
22
|
|
|
12
|
-
Descreva cada apontamento com estes campos
|
|
23
|
+
Descreva cada apontamento com estes campos, como nos exemplos:
|
|
13
24
|
|
|
14
25
|
```text
|
|
15
26
|
{"type": "enhancement", "behavior": "o comportamento em uma frase.", "file": "src/onde-vive.ts", "kind": "ui|sdk|driver|infra"}
|
|
16
|
-
{"type": "bug", "behavior": "o que quebra ou falta.", "file": "onde se manifesta.", "note": "
|
|
27
|
+
{"type": "bug", "behavior": "o que quebra ou falta.", "file": "onde se manifesta.", "note": "como reproduzir e o contorno aplicado."}
|
|
17
28
|
```
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
O pacote não coleta apontamentos do seu projeto automaticamente.
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
## Seguir sem esperar a correção
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
2. **Entregue** a feature com o workaround.
|
|
25
|
-
3. **Aponte** na Issue e siga em frente.
|
|
34
|
+
Uma lacuna ou um defeito do Opus não precisa bloquear a entrega:
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
1. **Contorne no projeto:** componha um wrapper local. O Opus é distribuído como código-fonte, então
|
|
37
|
+
qualquer superfície dele pode ser envolvida. Não edite `node_modules`, porque a alteração se
|
|
38
|
+
perde na próxima instalação.
|
|
39
|
+
2. **Entregue** a funcionalidade com o contorno.
|
|
40
|
+
3. **Registre** o apontamento na Issue.
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
A correção no Opus acontece em paralelo. Enquanto ela não sai, o contorno continua no projeto.
|
|
30
43
|
|
|
31
|
-
|
|
44
|
+
## Como o apontamento termina
|
|
32
45
|
|
|
33
|
-
- **
|
|
34
|
-
|
|
46
|
+
- Um **enhancement** aceito é implementado no Opus e publicado em uma nova versão. Ao
|
|
47
|
+
[atualizar o Opus](/docs/upgrading), o projeto troca o contorno pelo import.
|
|
48
|
+
- Um **bug** vira uma correção com entrada no `CHANGELOG`; na atualização, o contorno sai.
|
|
35
49
|
|
|
36
|
-
A
|
|
50
|
+
A decisão sobre o que entra no Opus fica com a Softize, que mantém o pacote. O registro das peças
|
|
51
|
+
incorporadas está no `PROMOTED.md` do pacote.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Produtos de Dados
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Produtos de Dados
|
|
6
|
+
|
|
7
|
+
Use um Produto de Dados quando um conjunto de dados precisar de identidade estável para catálogos,
|
|
8
|
+
agentes e revisão, independentemente da action que o entrega. A declaração diz o que o conjunto
|
|
9
|
+
representa, de onde vem, quem responde por ele e quais actions o disponibilizam. Ela não é uma
|
|
10
|
+
consulta, uma tabela nem uma view, e não executa nada: cada action continua sendo a fronteira de
|
|
11
|
+
execução, recorte e autorização.
|
|
12
|
+
|
|
13
|
+
## Declarar e registrar
|
|
14
|
+
|
|
15
|
+
`defineDataProduct` declara o produto e valida seus campos no momento em que roda. O domínio o
|
|
16
|
+
registra em `dataProducts`, ao lado das actions e entidades citadas:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { defineDataProduct, defineDomain } from '@softize/opus/core'
|
|
20
|
+
import { LeadEntity } from './entity.ts' // defineEntity({ name: 'lead', … })
|
|
21
|
+
import { leadList } from './actions/list.ts' // action 'lead.list'
|
|
22
|
+
import { leadFunnel } from './actions/funnel.ts' // action 'lead.funnel'
|
|
23
|
+
|
|
24
|
+
export const salesLeads = defineDataProduct({
|
|
25
|
+
id: 'sales.leads',
|
|
26
|
+
version: 1,
|
|
27
|
+
label: 'Leads comerciais',
|
|
28
|
+
description: 'Leads e seus resultados comerciais.',
|
|
29
|
+
owner: 'Vendas',
|
|
30
|
+
grain: 'Um lead.',
|
|
31
|
+
classification: 'internal',
|
|
32
|
+
nature: 'real',
|
|
33
|
+
sources: [{ id: 'crm', label: 'CRM comercial' }],
|
|
34
|
+
entities: ['lead'],
|
|
35
|
+
access: {
|
|
36
|
+
permissionContexts: ['sales'],
|
|
37
|
+
organizationalScopes: ['unit', 'team'],
|
|
38
|
+
},
|
|
39
|
+
interfaces: ['lead.list', 'lead.funnel'],
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export const leadDomain = defineDomain({
|
|
43
|
+
name: 'lead',
|
|
44
|
+
entities: { LeadEntity },
|
|
45
|
+
actions: { leadList, leadFunnel },
|
|
46
|
+
dataProducts: { salesLeads },
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`interfaces` e `entities` usam os nomes declarados em `bindAction`/`defineContract` e em
|
|
51
|
+
`defineEntity`, não nomes de arquivo ou de tabela. `sources` identifica os sistemas ou conjuntos de
|
|
52
|
+
origem; uma fonte não precisa ser uma entidade do Opus.
|
|
53
|
+
|
|
54
|
+
## Verificações
|
|
55
|
+
|
|
56
|
+
A declaração é conferida em três momentos:
|
|
57
|
+
|
|
58
|
+
- `defineDataProduct` lança `TypeError` quando o `id` não tem namespace (como `sales.leads`), a
|
|
59
|
+
`version` não é um inteiro positivo, falta texto obrigatório, `sources` ou `interfaces` estão
|
|
60
|
+
vazias ou há itens repetidos.
|
|
61
|
+
- `defineDomain` recusa um produto duplicado no domínio e referências a actions ou entidades que o
|
|
62
|
+
domínio não declara (`domain.data_product_interface_not_found` e
|
|
63
|
+
`domain.data_product_entity_not_found`).
|
|
64
|
+
- `opus check` lê o código sem executá-lo e confere exportação, `id`, `version`, duplicidade e as
|
|
65
|
+
referências a actions e entidades do projeto, com as regras de prefixo `data-product`. Para isso,
|
|
66
|
+
o produto precisa ser `export const` e usar valores literais.
|
|
67
|
+
|
|
68
|
+
## Acesso e segurança
|
|
69
|
+
|
|
70
|
+
`access` documenta o alcance esperado de forma verificável. Ele ajuda o catálogo e a revisão a
|
|
71
|
+
perceber, por exemplo, que uma interface deveria respeitar unidade e equipe. `access` não é RLS nem
|
|
72
|
+
autorização automática: cada action listada em `interfaces` precisa aplicar o próprio `authorize` e
|
|
73
|
+
os recortes no handler ou no repositório, inclusive quando chamada por IA ou por
|
|
74
|
+
[MCP](/docs/mcp).
|
|
75
|
+
|
|
76
|
+
`permissionContexts` nomeia chaves do vocabulário de permissão do projeto. Ele não descreve área,
|
|
77
|
+
departamento nem contexto de tela; um projeto que não usa essa dimensão declara uma lista vazia.
|
|
78
|
+
O nome antigo `contexts` ainda é aceito e projetado para compatibilidade, mas declarações novas usam
|
|
79
|
+
`permissionContexts`.
|
|
80
|
+
|
|
81
|
+
## Onde o produto aparece
|
|
82
|
+
|
|
83
|
+
- `opus gen` publica os produtos no `.opus/manifest.json`, e `opus introspect` os inclui no modelo
|
|
84
|
+
do projeto. Veja [CLI opus](/docs/cli).
|
|
85
|
+
- Em `runtime.aiTools()`, cada action exposta à IA traz em `metadata.dataProducts` os IDs dos
|
|
86
|
+
produtos relacionados e, em `metadata.dataProductLabels`, o `label` de cada ID. Veja
|
|
87
|
+
[IA generativa](/docs/ai).
|
|
88
|
+
- No servidor MCP, as mesmas informações aparecem em `_meta['com.softize.opus/data-products']` e
|
|
89
|
+
`_meta['com.softize.opus/data-product-labels']`.
|
|
90
|
+
|
|
91
|
+
Esses metadados servem a quem hospeda o agente: mostre o `label` às pessoas e use o ID na execução e
|
|
92
|
+
na auditoria. Eles não entram no prompt e não concedem acesso. Nenhuma dessas projeções é a fonte
|
|
93
|
+
da verdade: para mudar um produto, corrija a declaração e gere de novo.
|
|
94
|
+
|
|
95
|
+
## Evoluir um produto
|
|
96
|
+
|
|
97
|
+
Incremente `version` quando a forma observável do produto mudar. Para substituí-lo, mantenha a
|
|
98
|
+
declaração anterior com `status: 'deprecated'` e `replacedBy` apontando para o novo ID durante a
|
|
99
|
+
migração. Um produto ativo não declara `replacedBy`.
|
|
100
|
+
|
|
101
|
+
## Referência de DataProductConfig
|
|
102
|
+
|
|
103
|
+
| Campo | Contrato |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `id` | Identidade estável com namespace, como `sales.leads`. |
|
|
106
|
+
| `version` | Inteiro positivo; muda com a forma observável do produto. |
|
|
107
|
+
| `label`, `description` | Nome e descrição para pessoas. |
|
|
108
|
+
| `owner` | Responsável técnico ou de negócio. |
|
|
109
|
+
| `grain` | O que uma linha ou observação representa. |
|
|
110
|
+
| `classification`, `nature` | Classificação (como `internal` ou `personal`) e natureza (como `real`, `demo` ou `synthetic`) definidas pelo projeto. |
|
|
111
|
+
| `sources` | Pelo menos uma fonte `{ id, label, description? }`, sem `id` repetido. |
|
|
112
|
+
| `entities` | Nomes das entidades Opus que estruturam o produto; pode ser vazia. |
|
|
113
|
+
| `access` | `permissionContexts` e `organizationalScopes`, ambos listas de texto. |
|
|
114
|
+
| `interfaces` | Nomes das actions que disponibilizam o produto; pelo menos uma. |
|
|
115
|
+
| `status`, `replacedBy` | `active` (padrão) ou `deprecated`; um produto `deprecated` exige `replacedBy`. |
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
## Estados
|
|
2
2
|
|
|
3
|
-
Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta, na escala
|
|
4
|
+
de uma seção. Para o estado da página inteira, use `PageState`, descrito em [`Page`](/ui/page); para
|
|
5
|
+
uma coleção carregada por uma action `kind: 'list'`, [`ActionList`](/ui/action-list) já trata esses
|
|
6
|
+
estados.
|
|
7
|
+
|
|
8
|
+
Cada estado tem uma apresentação fixa:
|
|
9
|
+
|
|
10
|
+
- **Carregando:** um contêiner `role="status"` com o [`Spinner`](/ui/spinner) centralizado. Quando o
|
|
11
|
+
formato do conteúdo já é conhecido, em vez de passar `loading`, renderize o
|
|
12
|
+
[`Skeleton`](/ui/skeleton) durante a espera: ele reproduz esse formato e evita o salto de layout.
|
|
13
|
+
- **Vazio:** um [`Empty`](/ui/empty) com moldura sólida e `emptyMessage` como frase discreta.
|
|
14
|
+
- **Erro:** um [`Alert`](/ui/alert) de contexto `danger` com a mensagem segura de `errorMessage`,
|
|
15
|
+
sem expor o texto técnico de `error`. Com `onRetry`, o aviso ganha uma ação `ghost` só com ícone,
|
|
16
|
+
e `retryLabel` fornece o nome acessível e o tooltip dessa ação.
|
|
17
|
+
|
|
18
|
+
Quando mais de uma condição vale ao mesmo tempo, vence a primeira desta ordem: erro, carregando,
|
|
19
|
+
vazio. O conteúdo aparece somente quando nenhuma delas vale. Assim, uma nova tentativa com
|
|
20
|
+
`loading` e `error` ativos continua mostrando o erro até que `error` volte a `null`.
|
|
21
|
+
|
|
22
|
+
Para uma ação em andamento depois do clique, use `busy` em [`Button`](/ui/button).
|
|
9
23
|
|
|
10
24
|
```tsx preview col
|
|
11
25
|
<div className="w-full space-y-3">
|
|
@@ -32,9 +32,9 @@ export const SkillEntity = defineEntity({
|
|
|
32
32
|
|
|
33
33
|
## Código em camelCase, banco em snake_case
|
|
34
34
|
|
|
35
|
-
O schema tipado do Kysely e todas as consultas usam camelCase
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
O schema tipado do Kysely e todas as consultas usam camelCase (`workspaceId`, `ghRepo`). O banco
|
|
36
|
+
usa snake_case, definido na DDL do schema idempotente, como `db/schema.sql`. `CamelCasePlugin`
|
|
37
|
+
converte os nomes nas consultas e nos resultados. Um sink que não usa o Kysely com o
|
|
38
38
|
plugin, como `audit_log`, mantém os nomes do banco.
|
|
39
39
|
|
|
40
40
|
```ts
|
|
@@ -52,19 +52,20 @@ await db.selectFrom('agents').select(['isDefault', 'roleId']).where('workspaceId
|
|
|
52
52
|
|
|
53
53
|
Cada etapa possui um papel diferente:
|
|
54
54
|
|
|
55
|
-
- `opus db migrate`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- `opus seed`
|
|
59
|
-
|
|
55
|
+
- `opus db migrate` aplica o schema idempotente (`config.schema`), um único script SQL em que
|
|
56
|
+
`IF NOT EXISTS` e condições de guarda cobrem tanto o banco novo quanto a atualização. Em seguida,
|
|
57
|
+
compara entidades e banco e encerra com exit ≠ 0 quando há divergência.
|
|
58
|
+
- `opus seed` aplica datasets de desenvolvimento e teste declarados com `defineSeed` e registrados no
|
|
59
|
+
`opus.config.ts`. Ele só conecta com `NODE_ENV=development` ou `test`. Veja
|
|
60
|
+
[CLI opus](/docs/cli).
|
|
60
61
|
|
|
61
62
|
## Campos JSON
|
|
62
63
|
|
|
63
|
-
`kyselyRepo` serializa campos `t.json()` na escrita (`insert` e `update`), guiado pela
|
|
64
|
-
declaração. Sem
|
|
65
|
-
array do
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
`kyselyRepo` serializa os campos `t.json()` na escrita (`insert` e `update`), guiado pela
|
|
65
|
+
declaração da entidade. Sem essa etapa, o driver `pg` converte um objeto simples, mas transforma um
|
|
66
|
+
array em literal de array do Postgres, o que é inválido para `jsonb` e não aparece no typecheck.
|
|
67
|
+
Uma string passa sem alteração, o que preserva valores já serializados; na leitura, o `pg` devolve
|
|
68
|
+
o objeto. Uma consulta escrita fora do repositório continua responsável pela própria serialização.
|
|
68
69
|
|
|
69
70
|
## SQL gerado por modelo
|
|
70
71
|
|
|
@@ -79,13 +80,13 @@ const bi = readOnlyContextPool({ pool: new Pool({ connectionString, max: 4 }) })
|
|
|
79
80
|
const { rows } = await bi.query(sqlDoLlm, { role: roleFor(ctx) })
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
1. **Pool com limite** (`max`
|
|
83
|
+
1. **Pool com limite** (`max`, `maxConcurrent` ou ambos): impede que as consultas consumam todas as
|
|
83
84
|
conexões do aplicativo.
|
|
84
|
-
2. **`BEGIN TRANSACTION READ ONLY
|
|
85
|
-
3. **`SET LOCAL ROLE` por transação
|
|
86
|
-
|
|
87
|
-
4. **Protocolo estendido
|
|
88
|
-
(`SELECT 1; DROP …`)
|
|
85
|
+
2. **`BEGIN TRANSACTION READ ONLY`:** o servidor rejeita tentativas de escrita.
|
|
86
|
+
3. **`SET LOCAL ROLE` por transação:** o contexto define o alcance da consulta. Crie os roles nas
|
|
87
|
+
suas migrações com grants fechados por padrão; sem isso, `sales_read` leria `hr_employees`.
|
|
88
|
+
4. **Protocolo estendido:** o SQL sempre recebe os valores separadamente, e múltiplas instruções
|
|
89
|
+
(`SELECT 1; DROP …`) são recusadas pelo próprio protocolo.
|
|
89
90
|
|
|
90
91
|
Ao terminar, `DISCARD ALL` limpa a conexão. Se a limpeza falhar, a conexão é descartada em vez de
|
|
91
92
|
voltar ao pool. `statement_timeout`, aplicado por transação, limita consultas demoradas; o padrão é
|