@softize/opus 12.10.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 +56 -0
- package/bin/lib/check.mjs +1098 -310
- package/bin/lib/copy.mjs +12 -5
- package/docs/adr/0003-dictionary-presentation-is-declared.md +3 -0
- package/docs/adr/0004-page-content-state-is-composed.md +65 -0
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +180 -0
- package/docs/adr/0006-semantic-context-precedes-visual-variant.md +182 -0
- 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/instructions/opus.md +5 -0
- package/registry/skills/build-opus-ui/SKILL.md +27 -16
- package/registry/skills/build-opus-ui/references/evaluations.md +16 -5
- package/registry/skills/build-opus-ui/references/ui-patterns.md +38 -15
- 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/registry/skills/model-opus-dictionary/SKILL.md +4 -2
- package/registry/skills/model-opus-dictionary/references/evaluations.md +4 -3
- package/registry/templates/app/src/App.tsx +1 -1
- package/src/core/dictionary.ts +52 -14
- package/src/core/index.ts +10 -0
- package/src/core/ui-context.ts +29 -0
- package/src/schema/drivers/zod.ts +17 -8
- package/src/ui/components/patterns/action-form-card.tsx +18 -12
- package/src/ui/components/patterns/confirm.tsx +163 -40
- package/src/ui/components/patterns/content-header.tsx +335 -61
- package/src/ui/components/patterns/data-state.tsx +23 -10
- package/src/ui/components/patterns/list.tsx +1097 -783
- package/src/ui/components/patterns/page-state.tsx +115 -0
- package/src/ui/components/patterns/page.tsx +231 -41
- package/src/ui/components/patterns/sidebar.tsx +357 -83
- package/src/ui/components/patterns/trigger.tsx +37 -30
- package/src/ui/components/patterns/view.tsx +7 -11
- package/src/ui/components/primitives/alert.tsx +298 -110
- package/src/ui/components/primitives/ask.tsx +2 -1
- package/src/ui/components/primitives/badge.tsx +91 -30
- package/src/ui/components/primitives/button.tsx +99 -60
- package/src/ui/components/primitives/calendar.tsx +39 -39
- package/src/ui/components/primitives/card.tsx +96 -23
- package/src/ui/components/primitives/detail.tsx +2 -2
- package/src/ui/components/primitives/dialog.tsx +196 -39
- package/src/ui/components/primitives/dictionary-value.tsx +9 -14
- package/src/ui/components/primitives/dot.tsx +74 -21
- package/src/ui/components/primitives/drawer.tsx +40 -24
- package/src/ui/components/primitives/empty.tsx +3 -3
- package/src/ui/components/primitives/item.tsx +135 -79
- package/src/ui/components/primitives/menu.tsx +11 -3
- package/src/ui/components/primitives/metric-card.tsx +133 -0
- package/src/ui/components/primitives/sonner.tsx +187 -8
- package/src/ui/components/primitives/table.tsx +2 -2
- package/src/ui/docs/DocBrowser.tsx +104 -25
- package/src/ui/docs/changelog.tsx +1 -1
- 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 +54 -28
- 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 +21 -22
- package/src/ui/docs/content/breadcrumb.md +13 -8
- package/src/ui/docs/content/button.md +93 -15
- package/src/ui/docs/content/calendar.md +5 -5
- package/src/ui/docs/content/card.md +6 -6
- 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 +44 -0
- package/src/ui/docs/content/copyable.md +4 -3
- package/src/ui/docs/content/customization.md +7 -7
- 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 +8 -5
- package/src/ui/docs/content/dialog.md +339 -31
- package/src/ui/docs/content/dictionary-value.md +19 -18
- package/src/ui/docs/content/dock.md +3 -3
- package/src/ui/docs/content/dot.md +7 -7
- package/src/ui/docs/content/drawer.md +32 -16
- 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 +64 -24
- 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 +36 -17
- package/src/ui/docs/content/metric-card.md +41 -0
- package/src/ui/docs/content/observability.md +2 -2
- package/src/ui/docs/content/page.md +93 -10
- 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/semantic-context.md +63 -0
- package/src/ui/docs/content/separator.md +5 -5
- package/src/ui/docs/content/sidebar.md +325 -56
- package/src/ui/docs/content/skeleton.md +5 -4
- 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 +16 -6
- 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 +31 -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/doc-client.tsx +2 -2
- package/src/ui/docs/registry.tsx +580 -229
- package/src/ui/lib/semantic-context.ts +30 -0
- package/src/ui/meta.ts +278 -286
- package/src/ui/react.tsx +377 -111
- package/src/ui/theme.css +116 -0
- package/src/ui/components/primitives/alert-dialog.tsx +0 -190
- 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 -78
- package/src/ui/docs/content/toggle-group.md +0 -81
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
## Lista filtrável inline
|
|
2
2
|
|
|
3
|
-
Digite
|
|
3
|
+
Digite para filtrar uma coleção com navegação por teclado, grupos e estado vazio. Command é a base
|
|
4
|
+
do Select pesquisável; para uma escolha em formulário, use Select.
|
|
4
5
|
|
|
5
6
|
```tsx preview
|
|
6
7
|
<Command className="max-w-sm rounded-lg border border-border">
|
|
@@ -22,7 +23,7 @@ Digite pra filtrar — navegação por teclado, grupos e CommandEmpty de graça
|
|
|
22
23
|
|
|
23
24
|
## Palette modal (CommandDialog)
|
|
24
25
|
|
|
25
|
-
O Command embrulhado
|
|
26
|
+
O Command embrulhado em um Dialog, com header sr-only para a11y. O atalho ⌘K (keydown no app) só troca o open — o conteúdo é o mesmo do inline.
|
|
26
27
|
|
|
27
28
|
```tsx preview
|
|
28
29
|
const [open, setOpen] = useState(false)
|
|
@@ -46,11 +47,18 @@ render(
|
|
|
46
47
|
)
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
##
|
|
50
|
+
## Propriedades de CommandDialog
|
|
50
51
|
|
|
51
|
-
|
|
|
52
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
52
53
|
|---|---|---|---|
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
54
|
+
| `open` | `boolean` | | Estado do modal no modo controlado. |
|
|
55
|
+
| `onOpenChange` | `(open: boolean) => void` | | Atualiza o estado do modal; pode ser conectado ao atalho do aplicativo. |
|
|
56
|
+
| `title` | `string` | `'Comandos'` | Nome acessível do diálogo, disponível para leitura assistiva. |
|
|
57
|
+
| `description` | `string` | `'Busque um comando para executar.'` | Descrição acessível do diálogo. |
|
|
58
|
+
| `showCloseButton` | `boolean` | `true` | Exibe o botão de fechamento. |
|
|
59
|
+
|
|
60
|
+
## Propriedades de CommandItem
|
|
61
|
+
|
|
62
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `onSelect` | `(value: string) => void` | | Chamado ao selecionar o item por clique ou teclado. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Envio de texto
|
|
2
2
|
|
|
3
|
-
A caixa de escrever da casa: textarea
|
|
3
|
+
A caixa de escrever da casa: textarea em uma pílula elevada (`rounded-xl` + `border` + `shadow-sm`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/components/chat) extraído — use sozinho quando há entrada de texto mas não um chat (ex.: criar uma sessão). Controlado: o dono do texto é você. Os callbacks opcionais `onHistoryPrevious` e `onHistoryNext` permitem que esse dono consuma **↑/↓**; sem eles, as setas mantêm o comportamento nativo da textarea.
|
|
4
4
|
|
|
5
5
|
```tsx preview col
|
|
6
6
|
const [text, setText] = React.useState('')
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
## Região de conteúdo
|
|
2
|
+
|
|
3
|
+
Use `Content` para dar título e estrutura a uma região dentro de `PageBody`, `CardBody`,
|
|
4
|
+
`DialogBody` ou outra superfície. Ele renderiza uma `section` ligada ao próprio título e mantém o
|
|
5
|
+
espaçamento entre header e body. `ContentHeader` pertence sempre a essa estrutura; não o use solto.
|
|
6
|
+
|
|
7
|
+
No caso comum, prefira o shorthand:
|
|
8
|
+
|
|
9
|
+
```tsx preview col
|
|
10
|
+
render(
|
|
11
|
+
<Content
|
|
12
|
+
title="Dispositivos conectados"
|
|
13
|
+
description="Sessões com acesso à sua conta."
|
|
14
|
+
actions={<Button variant="outline">Encerrar outras sessões</Button>}
|
|
15
|
+
>
|
|
16
|
+
<div className="rounded-lg border border-border p-4">MacBook Para o · ativo agora</div>
|
|
17
|
+
</Content>,
|
|
18
|
+
)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Composição explícita
|
|
22
|
+
|
|
23
|
+
Use os slots quando o header precisar de composição própria. A árvore aceita exatamente um
|
|
24
|
+
`ContentHeader` e um `ContentBody` como filhos diretos. O header exige um `ContentTitle` e aceita
|
|
25
|
+
uma descrição, um metadado e uma região de ações.
|
|
26
|
+
|
|
27
|
+
```tsx preview col
|
|
28
|
+
render(
|
|
29
|
+
<Content level={2}>
|
|
30
|
+
<ContentHeader>
|
|
31
|
+
<ContentTitle>Dispositivos conectados</ContentTitle>
|
|
32
|
+
<ContentMeta>3</ContentMeta>
|
|
33
|
+
<ContentDescription>Sessões com acesso à sua conta.</ContentDescription>
|
|
34
|
+
<ContentActions><Button variant="outline">Atualizar</Button></ContentActions>
|
|
35
|
+
</ContentHeader>
|
|
36
|
+
<ContentBody>
|
|
37
|
+
<div className="rounded-lg border border-border p-4">MacBook Para o · ativo agora</div>
|
|
38
|
+
</ContentBody>
|
|
39
|
+
</Content>,
|
|
40
|
+
)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
As duas formas geram os mesmos elementos, estilos e `data-slot`. `opus check` reprova slots fora
|
|
44
|
+
do pai correto, filhos estruturais indiretos e a mistura de shorthand com composição explícita.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Copiar por ícone
|
|
2
2
|
|
|
3
|
-
Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5s. Bom
|
|
3
|
+
Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5s. Bom para toolbar ou célula estreita, ao lado de um ID/token/slug.
|
|
4
4
|
|
|
5
5
|
```tsx preview
|
|
6
6
|
<Copyable value="opus_sk_1a2b3c4d5e6f" className="text-muted-foreground hover:text-foreground" />
|
|
@@ -21,7 +21,8 @@ Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar n
|
|
|
21
21
|
|
|
22
22
|
## Duração do feedback
|
|
23
23
|
|
|
24
|
-
`feedbackMs` ajusta quanto
|
|
24
|
+
`feedbackMs` ajusta por quanto tempo a confirmação aparece; o padrão é 1500 milissegundos. Se a
|
|
25
|
+
área de transferência não estiver disponível, a ação não produz efeito.
|
|
25
26
|
|
|
26
27
|
```tsx preview
|
|
27
28
|
<Copyable value="copiado devagar" feedbackMs={3000} className="text-muted-foreground hover:text-foreground">
|
|
@@ -6,7 +6,7 @@ title: Customização
|
|
|
6
6
|
|
|
7
7
|
Cinco alavancas, do global ao pontual — e um limite de propósito. O caminho previsto é compor
|
|
8
8
|
e configurar, nunca forkar componente: o que não cabe nas alavancas evolui no Opus (com
|
|
9
|
-
divergência declarada),
|
|
9
|
+
divergência declarada), para valer para casa toda.
|
|
10
10
|
|
|
11
11
|
## 1 · Identidade por tokens
|
|
12
12
|
|
|
@@ -36,7 +36,7 @@ de 16, sem transformar esse valor em uma regra da biblioteca.
|
|
|
36
36
|
## 2 · className em tudo
|
|
37
37
|
|
|
38
38
|
> Todo componente termina em `cn(base, className)` com tailwind-merge: o utilitário do consumidor
|
|
39
|
-
> vence o conflito.
|
|
39
|
+
> vence o conflito. Para layout local (largura, margem, grid) — não para repintar o visual da casa.
|
|
40
40
|
|
|
41
41
|
```tsx
|
|
42
42
|
<Button className="w-full">Continuar</Button>
|
|
@@ -47,7 +47,7 @@ de 16, sem transformar esse valor em uma regra da biblioteca.
|
|
|
47
47
|
## 3 · Recomposição estrutural
|
|
48
48
|
|
|
49
49
|
> Os componentes são explodidos em slots. `asChild` (Radix Slot) renderiza como outro elemento
|
|
50
|
-
> mantendo estilo e comportamento; os `*Variants` aplicam a cara da casa
|
|
50
|
+
> mantendo estilo e comportamento; os `*Variants` aplicam a cara da casa em um elemento arbitrário.
|
|
51
51
|
|
|
52
52
|
```tsx preview
|
|
53
53
|
<Button asChild variant="outline">
|
|
@@ -59,13 +59,13 @@ de 16, sem transformar esse valor em uma regra da biblioteca.
|
|
|
59
59
|
// Slots: só o que a tela pede.
|
|
60
60
|
<Card>
|
|
61
61
|
<CardHeader><CardTitle>Workspace</CardTitle></CardHeader>
|
|
62
|
-
<
|
|
62
|
+
<CardBody>…</CardBody>
|
|
63
63
|
</Card>
|
|
64
64
|
|
|
65
65
|
// asChild: o filho VIRA o botão (sem forkar estilo).
|
|
66
66
|
<Button asChild><a href="/docs">Abrir documentação</a></Button>
|
|
67
67
|
|
|
68
|
-
// buttonVariants: a cara da casa
|
|
68
|
+
// buttonVariants: a cara da casa em um elemento qualquer.
|
|
69
69
|
<a className={buttonVariants({ variant: 'outline' })}>Link estilizado</a>
|
|
70
70
|
```
|
|
71
71
|
|
|
@@ -110,9 +110,9 @@ const [open, setOpen] = useState(false)
|
|
|
110
110
|
> do botão, a seta do tooltip — é identidade da casa, igual em todo projeto.
|
|
111
111
|
|
|
112
112
|
```tsx preview
|
|
113
|
-
<Badge
|
|
113
|
+
<Badge context="success">Cabe nas alavancas</Badge>
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
Se uma necessidade real não cabe nas alavancas (tokens · className · slots/asChild · props ·
|
|
117
117
|
contrato), o movimento não é dialeto local: é evoluir o componente **no Opus**, com a divergência
|
|
118
|
-
declarada (skill `build-opus-ui`) — assim a mudança vale
|
|
118
|
+
declarada (skill `build-opus-ui`) — assim a mudança vale para casa toda, e esta doc passa a mostrá-la.
|
|
@@ -16,9 +16,9 @@ No seu projeto, uma linha por apontamento em `.opus/issues.jsonl` na raiz do rep
|
|
|
16
16
|
|
|
17
17
|
## Não trave esperando
|
|
18
18
|
|
|
19
|
-
O ponto do ciclo é **não bloquear a entrega**. Bateu
|
|
19
|
+
O ponto do ciclo é **não bloquear a entrega**. Bateu em um gap ou em um bug do Opus:
|
|
20
20
|
|
|
21
|
-
1. **Contorne local** — componha um wrapper no seu projeto. O Opus entrega _source_, então dá
|
|
21
|
+
1. **Contorne local** — componha um wrapper no seu projeto. O Opus entrega _source_, então dá para embrulhar qualquer superfície dele. Nunca edite `node_modules` (some no próximo install).
|
|
22
22
|
2. **Entregue** a feature com o workaround.
|
|
23
23
|
3. **Aponte** no `.opus/issues.jsonl` e siga em frente.
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ O "depois" — o conserto no Opus — corre em paralelo. Ele não segura o seu t
|
|
|
28
28
|
|
|
29
29
|
O apontamento é colhido e abre uma **Issue no repositório do Opus**, onde a triagem acontece (deduplicada — re-apontar o mesmo é idempotente):
|
|
30
30
|
|
|
31
|
-
- **Enhancement** aceito (com reincidência) → implementado no Opus → sai
|
|
31
|
+
- **Enhancement** aceito (com reincidência) → implementado no Opus → sai em um _bump_ → seu projeto atualiza o pin (`opus.json`) e troca o workaround pelo import.
|
|
32
32
|
- **Bug** → vira _fix_ + entrada no `CHANGELOG` → no _bump_, o workaround sai.
|
|
33
33
|
|
|
34
34
|
A régua e a decisão ficam com quem mantém o Opus — hoje, a **Softize**. O registro curado das promoções vive no `PROMOTED.md` do pacote.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
## Estados
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta. O
|
|
4
|
+
carregamento usa `Spinner`; o vazio preserva uma moldura sólida; e o erro apresenta uma mensagem
|
|
5
|
+
segura, sem expor detalhes técnicos. Para uma ação em andamento depois do clique, use `busy` em
|
|
6
|
+
`Button`.
|
|
7
7
|
|
|
8
8
|
```tsx preview col
|
|
9
9
|
<div className="w-full space-y-3">
|
|
@@ -19,12 +19,11 @@ pro "depois" (ação em andamento), use o busy do Button.
|
|
|
19
19
|
</div>
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Dentro de uma tabela
|
|
23
23
|
|
|
24
|
-
Em
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
ou vínculo, com título, descrição ou ação, use `Empty`, cuja moldura é tracejada.
|
|
24
|
+
Em uma tabela já emoldurada, passe `colSpan` para ocupar uma linha inteira dentro de `<tbody>`. A
|
|
25
|
+
tabela continua responsável pela borda, evitando uma segunda moldura no estado vazio. Para uma
|
|
26
|
+
região disponível para criação ou vínculo, use `Empty`.
|
|
28
27
|
|
|
29
28
|
```tsx preview col
|
|
30
29
|
<table className="w-full overflow-hidden rounded-lg border border-border text-sm">
|
|
@@ -38,13 +37,13 @@ ou vínculo, com título, descrição ou ação, use `Empty`, cuja moldura é tr
|
|
|
38
37
|
</table>
|
|
39
38
|
```
|
|
40
39
|
|
|
41
|
-
##
|
|
40
|
+
## Propriedades de DataState
|
|
42
41
|
|
|
43
|
-
|
|
|
42
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
44
43
|
|---|---|---|---|
|
|
45
44
|
| `loading` | `boolean` | | Carregando (antes do conteúdo) — mostra o Spinner centralizado. |
|
|
46
45
|
| `empty` | `boolean` | | Sem itens — mostra o emptyText. |
|
|
47
46
|
| `emptyText` | `string` | | Texto do vazio (pt-BR, ex.: "Nenhum papel."). |
|
|
48
|
-
| `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica
|
|
47
|
+
| `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica não vai para tela (use errorText). |
|
|
49
48
|
| `errorText` | `string` | `'Não foi possível carregar.'` | Aviso de erro, orientado ao usuário. |
|
|
50
49
|
| `colSpan` | `number` | | Em tabela: renderiza o estado como `<tr><td colSpan>` (cabe direto no tbody). |
|
|
@@ -4,15 +4,15 @@ title: Camada de dados
|
|
|
4
4
|
|
|
5
5
|
# Camada de dados
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
As entidades descrevem o armazenamento que o manifest projeta, enquanto o Kysely mantém as
|
|
8
|
+
consultas tipadas. No código, nomes usam camelCase; no banco, snake_case. `CamelCasePlugin` faz essa
|
|
9
|
+
conversão nos dois sentidos.
|
|
10
10
|
|
|
11
11
|
## Entidade e manifest
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
`defineEntity` declara campos e tipos lógicos `t.*`. A `description` de cada entidade e action
|
|
14
|
+
registra o significado de negócio projetado no manifest por `opus gen`. Use `opus db check` para
|
|
15
|
+
detectar divergências entre a entidade e o banco.
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
18
|
import { defineEntity } from '@softize/opus/schema'
|
|
@@ -30,16 +30,12 @@ export const SkillEntity = defineEntity({
|
|
|
30
30
|
})
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
## Código camelCase, banco
|
|
33
|
+
## Código em camelCase, banco em snake_case
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
O schema tipado do Kysely e **toda** query (select/insert/update/where) usam camelCase
|
|
35
|
+
O schema tipado do Kysely e todas as consultas usam camelCase
|
|
38
36
|
(`workspaceId`, `ghRepo`). O banco é snake (a DDL no schema idempotente — `db/schema.sql`).
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Única exceção: um sink escrito por fora do Kysely-com-plugin (ex.: o `audit_log` do Opus, pelo
|
|
42
|
-
pool) fica snake.
|
|
37
|
+
`CamelCasePlugin` converte nomes nas consultas e nos resultados. Um sink que não usa o Kysely com o
|
|
38
|
+
plugin, como `audit_log`, mantém os nomes do banco.
|
|
43
39
|
|
|
44
40
|
```ts
|
|
45
41
|
import { CamelCasePlugin, Kysely, PostgresDialect } from 'kysely'
|
|
@@ -52,31 +48,28 @@ const db = new Kysely<AdminDB>({
|
|
|
52
48
|
await db.selectFrom('agents').select(['isDefault', 'roleId']).where('workspaceId', '=', id).execute()
|
|
53
49
|
```
|
|
54
50
|
|
|
55
|
-
## Migrações
|
|
51
|
+
## Migrações, preparação e seeds
|
|
56
52
|
|
|
57
|
-
|
|
53
|
+
Cada etapa possui um papel diferente:
|
|
58
54
|
|
|
59
55
|
- `opus db migrate` — aplica o **schema idempotente** (`config.schema`, um script SQL
|
|
60
56
|
evolutivo: `IF NOT EXISTS` + guards cobrem nascer do zero e upgrade no mesmo artefato)
|
|
61
57
|
e roda o drift-check entidade ↔ banco na sequência (exit ≠ 0 se divergir).
|
|
62
58
|
- `prepare` — backfill estrutural, prod-safe e idempotente; roda no deploy (depois do migrate).
|
|
63
|
-
- `seed` —
|
|
64
|
-
|
|
65
|
-
## Campo `t.json()`: objeto entra, objeto sai
|
|
59
|
+
- `seed` — cria dados de desenvolvimento e teste. Não é executado em produção.
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
> no caminho do `kyselyRepo`.
|
|
61
|
+
## Campos JSON
|
|
69
62
|
|
|
70
|
-
|
|
63
|
+
`kyselyRepo` serializa campos `t.json()` na escrita (`insert` e `update`), guiado pela
|
|
71
64
|
declaração. Sem isso, o pg até stringifica objeto plano — mas **array vira literal de
|
|
72
|
-
array do PG** (errado
|
|
65
|
+
array do PG** (errado para jsonb) sem quebrar typecheck. String passa direto (quem já
|
|
73
66
|
mandava pré-serializado segue valendo); na leitura o pg devolve objeto. Query à mão
|
|
74
67
|
(fora do repo) continua responsável pelo próprio stringify.
|
|
75
68
|
|
|
76
|
-
## SQL
|
|
69
|
+
## SQL gerado por modelo
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
Uma action `ai: true` que executa SQL livre precisa de quatro proteções no banco. Validar o texto
|
|
72
|
+
com expressão regular não substitui nenhuma delas:
|
|
80
73
|
|
|
81
74
|
```ts
|
|
82
75
|
import { Pool } from 'pg'
|
|
@@ -86,14 +79,14 @@ const bi = readOnlyContextPool({ pool: new Pool({ connectionString, max: 4 }) })
|
|
|
86
79
|
const { rows } = await bi.query(sqlDoLlm, { role: roleFor(ctx) })
|
|
87
80
|
```
|
|
88
81
|
|
|
89
|
-
1. **Pool com
|
|
90
|
-
conexões do
|
|
82
|
+
1. **Pool com limite** (`max` e/ou `maxConcurrent`) — impede que as consultas consumam todas as
|
|
83
|
+
conexões do aplicativo.
|
|
91
84
|
2. **`BEGIN TRANSACTION READ ONLY`** — o servidor rejeita tentativas de escrita;
|
|
92
|
-
3. **`SET LOCAL ROLE` por transação** — o alcance é
|
|
85
|
+
3. **`SET LOCAL ROLE` por transação** — o alcance é definido pelo contexto (crie os roles com grants
|
|
93
86
|
default-fechado nas suas migrações: sem isso, `sales_read` leria `hr_employees`);
|
|
94
|
-
4. **Protocolo estendido** — o SQL
|
|
87
|
+
4. **Protocolo estendido** — o SQL sempre recebe os valores separadamente; múltiplas instruções
|
|
95
88
|
(`SELECT 1; DROP …`) é recusada pelo próprio protocolo.
|
|
96
89
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
Ao terminar, `DISCARD ALL` limpa a conexão. Se a limpeza falhar, a conexão é descartada em vez de
|
|
91
|
+
voltar ao pool. `statement_timeout`, aplicado por transação, limita consultas demoradas; o padrão é
|
|
92
|
+
15 segundos.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
DetailGroup
|
|
2
|
-
DetailField representa cada par e aceita conteúdo React
|
|
3
|
-
|
|
1
|
+
Use `DetailGroup` para organizar dados somente leitura como pares de rótulo e valor.
|
|
2
|
+
`DetailField` representa cada par e aceita conteúdo React em `label` e `value`. Para entrada e
|
|
3
|
+
validação, use `Field`.
|
|
4
4
|
|
|
5
5
|
```tsx preview col
|
|
6
6
|
<DetailGroup columns={2}>
|
|
@@ -9,7 +9,7 @@ para entrada e validação; use DetailField quando a pessoa apenas consulta um v
|
|
|
9
9
|
label="Estágio"
|
|
10
10
|
value={
|
|
11
11
|
<DictionaryValue
|
|
12
|
-
dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospect' }, customer: { label: 'Cliente',
|
|
12
|
+
dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospect' }, customer: { label: 'Cliente', context: 'success' } }, presentation: 'stage' }}
|
|
13
13
|
value="prospect"
|
|
14
14
|
/>
|
|
15
15
|
}
|
|
@@ -39,7 +39,10 @@ significado no domínio. `0` e `false` seguem como valores. Ver `EmptyValue`.
|
|
|
39
39
|
|
|
40
40
|
`variant="framed"` adiciona a superfície e a borda externa. `dividers` desenha apenas as
|
|
41
41
|
divisórias internas; as duas opções são independentes e podem ser combinadas. `orientation`
|
|
42
|
-
define se a chave fica sobre o valor ou ao lado dele.
|
|
42
|
+
define se a chave fica sobre o valor ou ao lado dele. Na orientação horizontal, todos os valores
|
|
43
|
+
começam depois da mesma coluna de rótulo, com largura padrão de `7rem`. Quando a superfície exigir
|
|
44
|
+
mais espaço para os rótulos, ajuste a variável no grupo, por exemplo com
|
|
45
|
+
`className="[--detail-label-width:9rem]"`.
|
|
43
46
|
|
|
44
47
|
```tsx preview col
|
|
45
48
|
<DetailGroup columns={2} orientation="horizontal" variant="framed" dividers>
|