@softize/opus 18.5.0 → 20.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +275 -0
- package/bin/cli.mjs +5 -0
- package/bin/lib/check.mjs +198 -10
- package/bin/lib/copy.mjs +29 -0
- package/bin/lib/mcp.mjs +1 -1
- package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +298 -0
- package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
- package/docs/data-products.md +15 -9
- package/docs/protocol.md +3 -4
- package/docs/releasing.md +2 -2
- package/package.json +2 -2
- package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
- package/registry/templates/app/package.json +1 -1
- package/src/core/runtime.ts +96 -8
- package/src/core/types.ts +5 -2
- package/src/ui/components/patterns/action-form-card.tsx +11 -1
- package/src/ui/components/patterns/content-header.tsx +16 -12
- package/src/ui/components/patterns/fill-region.ts +150 -0
- package/src/ui/components/patterns/heading-level.ts +16 -0
- package/src/ui/components/patterns/list.tsx +103 -73
- package/src/ui/components/patterns/page.tsx +20 -4
- package/src/ui/components/patterns/pane.tsx +294 -0
- package/src/ui/components/patterns/presentation.tsx +81 -54
- package/src/ui/components/patterns/sidebar.tsx +13 -72
- package/src/ui/components/patterns/split.tsx +51 -38
- package/src/ui/components/patterns/surface-anatomy.ts +246 -0
- package/src/ui/components/patterns/surface-header.tsx +51 -6
- package/src/ui/components/patterns/surface-media.tsx +69 -0
- package/src/ui/components/patterns/trigger.tsx +12 -2
- package/src/ui/components/primitives/alert.tsx +9 -1
- package/src/ui/components/primitives/avatar.tsx +7 -3
- package/src/ui/components/primitives/card.tsx +91 -29
- package/src/ui/components/primitives/carousel.tsx +34 -5
- package/src/ui/components/primitives/checkbox.tsx +11 -3
- package/src/ui/components/primitives/command.tsx +14 -5
- package/src/ui/components/primitives/copyable.tsx +120 -10
- package/src/ui/components/primitives/detail.tsx +10 -13
- package/src/ui/components/primitives/dialog.tsx +179 -45
- package/src/ui/components/primitives/drawer.tsx +131 -24
- package/src/ui/components/primitives/empty.tsx +1 -1
- package/src/ui/components/primitives/icon-picker.tsx +6 -2
- package/src/ui/components/primitives/icon-tile.tsx +7 -0
- package/src/ui/components/primitives/input-group.tsx +9 -2
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/kbd.tsx +1 -1
- package/src/ui/components/primitives/label.tsx +3 -0
- package/src/ui/components/primitives/menu.tsx +5 -0
- package/src/ui/components/primitives/metric-card.tsx +98 -39
- package/src/ui/components/primitives/popover.tsx +1 -1
- package/src/ui/components/primitives/progress.tsx +9 -1
- package/src/ui/components/primitives/slider.tsx +62 -11
- package/src/ui/components/primitives/truncate.tsx +10 -1
- package/src/ui/docs/content/accordion.md +74 -27
- package/src/ui/docs/content/action-form-card.md +24 -15
- package/src/ui/docs/content/action-form-dialog.md +17 -7
- package/src/ui/docs/content/action-form.md +92 -77
- package/src/ui/docs/content/action-list-dialog.md +75 -38
- package/src/ui/docs/content/action-list.md +134 -52
- package/src/ui/docs/content/action-trigger.md +27 -10
- package/src/ui/docs/content/action-view.md +14 -14
- package/src/ui/docs/content/actions.md +161 -66
- package/src/ui/docs/content/ai.md +43 -44
- package/src/ui/docs/content/alert.md +83 -29
- package/src/ui/docs/content/ask.md +51 -26
- package/src/ui/docs/content/aspect-ratio.md +22 -24
- package/src/ui/docs/content/audit.md +23 -19
- package/src/ui/docs/content/auth.md +38 -19
- package/src/ui/docs/content/avatar.md +54 -5
- package/src/ui/docs/content/badge.md +29 -9
- package/src/ui/docs/content/breadcrumb.md +21 -8
- package/src/ui/docs/content/button.md +73 -43
- package/src/ui/docs/content/calendar.md +134 -26
- package/src/ui/docs/content/card.md +252 -35
- package/src/ui/docs/content/carousel.md +37 -6
- package/src/ui/docs/content/chat.md +118 -37
- package/src/ui/docs/content/checkbox.md +73 -7
- package/src/ui/docs/content/cli.md +94 -55
- package/src/ui/docs/content/collapsible.md +43 -19
- package/src/ui/docs/content/command.md +30 -8
- package/src/ui/docs/content/composer.md +23 -8
- package/src/ui/docs/content/content.md +37 -7
- package/src/ui/docs/content/copyable.md +39 -8
- package/src/ui/docs/content/customization.md +55 -46
- package/src/ui/docs/content/cycle.md +33 -18
- package/src/ui/docs/content/data-products.md +115 -0
- package/src/ui/docs/content/data-state.md +20 -6
- package/src/ui/docs/content/data.md +20 -19
- package/src/ui/docs/content/detail.md +12 -5
- package/src/ui/docs/content/dialog.md +219 -28
- package/src/ui/docs/content/dictionary-value.md +4 -4
- package/src/ui/docs/content/dock.md +54 -19
- package/src/ui/docs/content/dot.md +35 -5
- package/src/ui/docs/content/drawer.md +193 -22
- package/src/ui/docs/content/empty-value.md +20 -6
- package/src/ui/docs/content/empty.md +24 -3
- package/src/ui/docs/content/events.md +76 -16
- package/src/ui/docs/content/executions.md +9 -9
- package/src/ui/docs/content/field.md +21 -6
- package/src/ui/docs/content/getting-started.md +44 -34
- package/src/ui/docs/content/icon-picker.md +19 -13
- package/src/ui/docs/content/icon-tile.md +28 -5
- package/src/ui/docs/content/input-otp.md +58 -7
- package/src/ui/docs/content/input.md +22 -3
- package/src/ui/docs/content/item.md +53 -6
- package/src/ui/docs/content/kbd.md +5 -1
- package/src/ui/docs/content/label.md +13 -4
- package/src/ui/docs/content/log.md +5 -5
- package/src/ui/docs/content/markdown.md +5 -4
- package/src/ui/docs/content/mcp.md +49 -23
- package/src/ui/docs/content/menu.md +95 -10
- package/src/ui/docs/content/metric-card.md +68 -21
- package/src/ui/docs/content/observability.md +10 -9
- package/src/ui/docs/content/page.md +125 -11
- package/src/ui/docs/content/pagination.md +13 -5
- package/src/ui/docs/content/pane.md +300 -0
- package/src/ui/docs/content/popover.md +42 -5
- package/src/ui/docs/content/presentation.md +91 -47
- package/src/ui/docs/content/progress.md +27 -14
- package/src/ui/docs/content/queue.md +39 -21
- package/src/ui/docs/content/radio-group.md +26 -4
- package/src/ui/docs/content/router.md +29 -10
- package/src/ui/docs/content/runtime.md +90 -54
- package/src/ui/docs/content/scheduler.md +53 -21
- package/src/ui/docs/content/scroll-area.md +42 -35
- package/src/ui/docs/content/select.md +78 -33
- package/src/ui/docs/content/semantic-context.md +21 -2
- package/src/ui/docs/content/separator.md +15 -9
- package/src/ui/docs/content/sidebar.md +25 -45
- package/src/ui/docs/content/skeleton.md +13 -4
- package/src/ui/docs/content/slider.md +37 -9
- package/src/ui/docs/content/spinner.md +4 -3
- package/src/ui/docs/content/split.md +34 -20
- package/src/ui/docs/content/storage.md +8 -9
- package/src/ui/docs/content/switch.md +10 -7
- package/src/ui/docs/content/table.md +46 -8
- package/src/ui/docs/content/tabs.md +14 -4
- package/src/ui/docs/content/testing.md +76 -31
- package/src/ui/docs/content/textarea.md +5 -4
- package/src/ui/docs/content/toast.md +48 -7
- package/src/ui/docs/content/toggle.md +3 -3
- package/src/ui/docs/content/tokens.md +15 -26
- package/src/ui/docs/content/tooltip.md +18 -2
- package/src/ui/docs/content/truncate.md +36 -6
- package/src/ui/docs/content/ui.md +74 -22
- package/src/ui/docs/content/upgrading.md +45 -36
- package/src/ui/docs/plugin.ts +36 -2
- package/src/ui/docs/registry.tsx +14 -2
- package/src/ui/drivers/react.tsx +16 -8
- package/src/ui/meta.ts +38 -32
- package/src/ui/react.tsx +14 -0
- package/src/vite/design.ts +16 -4
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
## Forma curta
|
|
2
2
|
|
|
3
|
+
Use `Alert` para manter uma informação visível no contexto da página até que ela deixe de valer.
|
|
4
|
+
Para confirmar o resultado de uma ação que a pessoa acabou de fazer, sem ocupar espaço na página,
|
|
5
|
+
use [`toast`](/ui/toast). Para o erro ou o vazio de uma consulta, use
|
|
6
|
+
[`DataState`](/ui/data-state), que já monta o `Alert` de erro com a ação de recuperação.
|
|
7
|
+
|
|
3
8
|
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
|
|
9
|
+
`icon`; o componente monta os slots e escolhe o papel acessível pelo `context`.
|
|
5
10
|
|
|
6
11
|
```tsx preview col
|
|
7
|
-
<Alert icon={<Info />} title="
|
|
12
|
+
<Alert icon={<Info />} title="Versão fixada" description="Este workspace usa a versão do Opus declarada em opus.json." />
|
|
8
13
|
<Alert
|
|
9
14
|
context="danger"
|
|
10
15
|
icon={<CircleAlert />}
|
|
@@ -25,12 +30,18 @@ Use a forma curta quando o aviso tiver ícone, título e uma frase. Declare `tit
|
|
|
25
30
|
/>
|
|
26
31
|
```
|
|
27
32
|
|
|
33
|
+
O `context` também decide como leitores de tela anunciam o aviso. Em `danger` e `warning`, o
|
|
34
|
+
`Alert` usa `role="alert"`: o anúncio interrompe o que a pessoa estiver ouvindo, porque o aviso pede
|
|
35
|
+
atenção imediata. Em `neutral`, `info` e `success`, usa `role="status"`: o anúncio espera a leitura
|
|
36
|
+
em curso terminar. Para outro comportamento, passe `role` ao componente; o valor informado substitui
|
|
37
|
+
o padrão do contexto.
|
|
38
|
+
|
|
28
39
|
## Só a frase
|
|
29
40
|
|
|
30
41
|
Título é opcional — o aviso de uma linha dispensa. O contexto continua visível por superfície,
|
|
31
42
|
borda e texto; o conteúdo comunica o significado sem depender somente da cor.
|
|
32
|
-
O Alert usa o corpo
|
|
33
|
-
densidade comum.
|
|
43
|
+
O `Alert` usa o texto de corpo das telas de operação (`text-sm`) e `0.75rem` de recuo interno; essa
|
|
44
|
+
já é a densidade comum, então não a reduza em cada uso.
|
|
34
45
|
|
|
35
46
|
```tsx preview col
|
|
36
47
|
<Alert description="Nenhuma sessão aberta neste repositório." />
|
|
@@ -41,8 +52,8 @@ densidade comum.
|
|
|
41
52
|
|
|
42
53
|
Sem `icon` o alert mantém somente a coluna de texto. Com ele, a forma curta materializa
|
|
43
54
|
`AlertMedia` à esquerda e `AlertHeader` à direita. A mídia mantém uma moldura quadrada de tamanho
|
|
44
|
-
estável, mesmo quando a descrição ocupa mais linhas; o título fica limitado a uma linha. Texto solto
|
|
45
|
-
vale (`<Alert>Sincronizado.</Alert>`) e se torna uma descrição.
|
|
55
|
+
estável, mesmo quando a descrição ocupa mais linhas; o título fica limitado a uma linha. Texto solto
|
|
56
|
+
como filho também vale (`<Alert>Sincronizado.</Alert>`) e se torna uma descrição.
|
|
46
57
|
|
|
47
58
|
```tsx preview col
|
|
48
59
|
<Alert title="Sem provider próprio" description="As conversas usam o padrão do sistema." />
|
|
@@ -51,15 +62,18 @@ vale (`<Alert>Sincronizado.</Alert>`) e se torna uma descrição.
|
|
|
51
62
|
|
|
52
63
|
## Composição (conteúdo rico)
|
|
53
64
|
|
|
54
|
-
Quando a mensagem precisa de conteúdo rico, componha os slots da família
|
|
55
|
-
|
|
56
|
-
`
|
|
57
|
-
|
|
65
|
+
Quando a mensagem precisa de conteúdo rico, componha os slots da família:
|
|
66
|
+
|
|
67
|
+
- `AlertMedia` recebe o ícone, pintado com a cor do contexto.
|
|
68
|
+
- `AlertHeader` agrupa `AlertTitle` e `AlertDescription`.
|
|
69
|
+
- `AlertActions` reúne as ações no fim lógico da superfície, centralizadas verticalmente na linha do
|
|
70
|
+
conteúdo — a mesma posição usada pelas ações do [Toast](/ui/toast).
|
|
58
71
|
|
|
59
72
|
Use `ghost` como tratamento padrão para ações dentro do alert: a mensagem já fornece a superfície e
|
|
60
73
|
o botão não precisa desenhar outra moldura. Mantenha texto quando a ação expressa um resultado
|
|
61
|
-
específico. Para tentar
|
|
62
|
-
botão só com ícone, nome acessível e tooltip.
|
|
74
|
+
específico. Para tentar de novo, recarregar ou repetir a operação descrita pelo aviso, prefira um
|
|
75
|
+
botão só com ícone, nome acessível e [tooltip](/ui/tooltip). O `TooltipProvider` montado uma vez na
|
|
76
|
+
raiz do aplicativo atende a esse tooltip.
|
|
63
77
|
|
|
64
78
|
```tsx preview col
|
|
65
79
|
<Alert context="danger">
|
|
@@ -67,30 +81,27 @@ botão só com ícone, nome acessível e tooltip.
|
|
|
67
81
|
<CircleAlert />
|
|
68
82
|
</AlertMedia>
|
|
69
83
|
<AlertHeader>
|
|
70
|
-
<AlertTitle>Não
|
|
84
|
+
<AlertTitle>Não foi possível publicar</AlertTitle>
|
|
71
85
|
<AlertDescription>
|
|
72
|
-
<p>O registry recusou a versão 3.0.0
|
|
73
|
-
<p>
|
|
86
|
+
<p>O registry recusou a versão 3.0.0 porque ela já existe.</p>
|
|
87
|
+
<p>Aumente o número da versão e tente de novo.</p>
|
|
74
88
|
</AlertDescription>
|
|
75
89
|
</AlertHeader>
|
|
76
90
|
<AlertActions>
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
</Tooltip>
|
|
86
|
-
</TooltipProvider>
|
|
91
|
+
<Tooltip>
|
|
92
|
+
<TooltipTrigger asChild>
|
|
93
|
+
<Button variant="ghost" size="icon" aria-label="Tentar de novo">
|
|
94
|
+
<RefreshCw />
|
|
95
|
+
</Button>
|
|
96
|
+
</TooltipTrigger>
|
|
97
|
+
<TooltipContent>Tentar de novo</TooltipContent>
|
|
98
|
+
</Tooltip>
|
|
87
99
|
</AlertActions>
|
|
88
100
|
</Alert>
|
|
89
101
|
```
|
|
90
102
|
|
|
91
103
|
A forma curta também aceita uma ação como `children`: com `title` ou `description`, esse conteúdo
|
|
92
|
-
entra depois da mensagem, na posição de `AlertActions`.
|
|
93
|
-
`AlertMedia`, `AlertHeader` ou `AlertActions`; essa mistura lança erro.
|
|
104
|
+
entra depois da mensagem, na posição de `AlertActions`.
|
|
94
105
|
|
|
95
106
|
```tsx preview col
|
|
96
107
|
<Alert
|
|
@@ -104,14 +115,57 @@ entra depois da mensagem, na posição de `AlertActions`. Não combine a forma c
|
|
|
104
115
|
</Alert>
|
|
105
116
|
```
|
|
106
117
|
|
|
118
|
+
## Composições que lançam erro
|
|
119
|
+
|
|
120
|
+
A família confere a própria estrutura ao renderizar e lança erro, com a regra violada na
|
|
121
|
+
mensagem, quando:
|
|
122
|
+
|
|
123
|
+
- a forma curta (`title` ou `description`) é combinada com `AlertMedia`, `AlertHeader`
|
|
124
|
+
ou `AlertActions`;
|
|
125
|
+
- a composição explícita recebe `icon` em vez de `AlertMedia`;
|
|
126
|
+
- a composição explícita não tem exatamente um `AlertHeader`, repete `AlertMedia` ou
|
|
127
|
+
`AlertActions`, ou recebe outro filho direto além desses três;
|
|
128
|
+
- `AlertMedia`, `AlertHeader` ou `AlertActions` aparecem fora de um `Alert`;
|
|
129
|
+
- `AlertTitle` ou `AlertDescription` aparecem fora de um `AlertHeader`;
|
|
130
|
+
- `AlertHeader` fica vazio, repete `AlertTitle` ou `AlertDescription`, ou recebe um elemento HTML
|
|
131
|
+
direto, como `<div>` ou `<p>`. Componentes próprios que envolvem o título ou a descrição são
|
|
132
|
+
aceitos.
|
|
133
|
+
|
|
107
134
|
## Propriedades de Alert
|
|
108
135
|
|
|
109
136
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
110
137
|
| ------------- | ----------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
111
|
-
| `title` | `React.ReactNode` | | Título do alert (a forma curta). Não é o atributo `title` do HTML
|
|
138
|
+
| `title` | `React.ReactNode` | | Título do alert (a forma curta). Não é o atributo `title` do HTML: o componente não aceita a dica nativa do navegador; use [`Tooltip`](/ui/tooltip). |
|
|
112
139
|
| `description` | `React.ReactNode` | | A frase. Sozinha, dispensa título. |
|
|
113
140
|
| `icon` | `React.ReactNode` | | Ícone à esquerda; materializa `AlertMedia` com realce tonal. Decorativo — quem nomeia é o título. |
|
|
114
|
-
| `context` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | O significado da mensagem e do realce do ícone.
|
|
141
|
+
| `context` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | O significado da mensagem e do realce do ícone. Também define o papel acessível: `role="alert"` em `danger` e `warning`, `role="status"` nos demais. |
|
|
142
|
+
| `role` | `React.AriaRole` | | Papel acessível da raiz. Quando informado, substitui o papel derivado do `context`. |
|
|
115
143
|
| `variant` | `'subtle' \| 'outline'` | `'subtle'` | O tratamento visual aplicado ao contexto. |
|
|
116
144
|
| `children` | `React.ReactNode` | | Composição explícita, texto cru ou, junto da forma curta, ações exibidas após a mensagem. |
|
|
117
145
|
| `style` | `React.CSSProperties` | | Ajuste excepcional da raiz; medidas escaláveis usam `rem`. |
|
|
146
|
+
|
|
147
|
+
## Propriedades de AlertMedia
|
|
148
|
+
|
|
149
|
+
`AlertMedia` recebe o ícone e o apresenta em uma moldura quadrada tingida pelo `context` do
|
|
150
|
+
`Alert`. A mídia é decorativa (`aria-hidden`): o título e a descrição comunicam o conteúdo. Não tem
|
|
151
|
+
props próprias; aceita as de um elemento `div`.
|
|
152
|
+
|
|
153
|
+
## Propriedades de AlertHeader
|
|
154
|
+
|
|
155
|
+
`AlertHeader` agrupa no máximo um `AlertTitle` e um `AlertDescription`, empilhados na coluna de
|
|
156
|
+
texto. Não tem props próprias; aceita as de um elemento `div`.
|
|
157
|
+
|
|
158
|
+
## Propriedades de AlertTitle
|
|
159
|
+
|
|
160
|
+
`AlertTitle` exibe o título em uma linha, com reticências quando não cabe. Não tem props próprias;
|
|
161
|
+
aceita as de um elemento `div`.
|
|
162
|
+
|
|
163
|
+
## Propriedades de AlertDescription
|
|
164
|
+
|
|
165
|
+
`AlertDescription` apresenta o texto de apoio em cor atenuada e aceita mais de um parágrafo. Não
|
|
166
|
+
tem props próprias; aceita as de um elemento `div`.
|
|
167
|
+
|
|
168
|
+
## Propriedades de AlertActions
|
|
169
|
+
|
|
170
|
+
`AlertActions` posiciona as ações depois do texto, centralizadas na linha do conteúdo. Não tem
|
|
171
|
+
props próprias; aceita as de um elemento `div`.
|
|
@@ -1,42 +1,67 @@
|
|
|
1
|
-
|
|
1
|
+
## Perguntas durante uma conversa
|
|
2
|
+
|
|
3
|
+
Use `Ask` quando um agente precisar de uma decisão da pessoa antes de continuar, como escolher o
|
|
4
|
+
ambiente ou as verificações de uma tarefa. Cada pergunta oferece opções clicáveis e um campo de
|
|
5
|
+
resposta livre. No Opus, o modelo pede essas respostas pela tool `ask_user`, e o backend as recebe
|
|
6
|
+
em `onAsk`, como descreve a página de [IA generativa](/docs/ai). `Ask` cuida apenas da
|
|
7
|
+
apresentação e da validação: levar as respostas até o backend e retomar a conversa continuam com o
|
|
8
|
+
aplicativo.
|
|
2
9
|
|
|
3
10
|
```tsx preview
|
|
4
|
-
const [answers, setAnswers] =
|
|
11
|
+
const [answers, setAnswers] = useState<AskAnswer[]>([])
|
|
5
12
|
|
|
6
13
|
render(
|
|
7
14
|
<Ask
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
questions={[
|
|
16
|
+
{
|
|
17
|
+
question: 'Qual ambiente devemos usar?',
|
|
18
|
+
header: 'Ambiente',
|
|
19
|
+
options: [
|
|
20
|
+
{ label: 'Produção', description: 'Usa dados e serviços reais.' },
|
|
21
|
+
{ label: 'Homologação', description: 'Usa o ambiente de validação.' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
question: 'Quais verificações devo executar?',
|
|
26
|
+
header: 'Verificações',
|
|
27
|
+
multiSelect: true,
|
|
28
|
+
options: [{ label: 'Testes' }, { label: 'Typecheck' }, { label: 'Build' }],
|
|
29
|
+
},
|
|
30
|
+
]}
|
|
31
|
+
answers={answers}
|
|
32
|
+
onChange={setAnswers}
|
|
33
|
+
onSubmit={(next) => console.log(next)}
|
|
27
34
|
/>,
|
|
28
35
|
)
|
|
29
36
|
```
|
|
30
37
|
|
|
31
|
-
Cada
|
|
38
|
+
Cada opção é um botão; a `description`, quando existe, aparece em um tooltip. Perguntas com
|
|
39
|
+
`multiSelect` aceitam várias opções, e as demais funcionam como grupo de escolha única, navegável
|
|
40
|
+
pelas setas. Abaixo das opções, o campo “Outro (opcional)…” recebe uma resposta livre.
|
|
41
|
+
|
|
42
|
+
## Envio e validação
|
|
43
|
+
|
|
44
|
+
O botão “Responder” envia as respostas. Cada pergunta exige pelo menos uma opção selecionada ou um
|
|
45
|
+
texto livre preenchido; ao tentar enviar sem isso, a pergunta mostra “Escolha uma opção ou escreva
|
|
46
|
+
uma resposta.” e `onSubmit` não é chamado. `Ask` aceita de uma a quatro perguntas; fora dessa faixa,
|
|
47
|
+
o envio fica bloqueado e o componente exibe o aviso correspondente.
|
|
48
|
+
|
|
49
|
+
`busy` indica que o envio está em andamento: o botão mostra o progresso e os controles ficam
|
|
50
|
+
bloqueados. `disabled` apenas bloqueia a interação. A ordem de `answers` acompanha a de
|
|
51
|
+
`questions`, e o `header` de cada resposta vem de `question.header`, ou do próprio enunciado quando
|
|
52
|
+
o `header` estiver vazio. O `header` não aparece na tela: ele identifica a resposta para o modelo.
|
|
32
53
|
|
|
33
54
|
## Propriedades de Ask
|
|
34
55
|
|
|
35
56
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
36
57
|
|---|---|---|---|
|
|
37
|
-
| `questions` | `AskQuestion[]` | |
|
|
58
|
+
| `questions` | `AskQuestion[]` | | De uma a quatro perguntas. Cada uma declara `question`, `options` (`{ label, description? }`) e, opcionalmente, `header` e `multiSelect`. |
|
|
38
59
|
| `answers` | `AskAnswer[]` | | Respostas controladas, na mesma ordem de `questions`; entradas ausentes contam como vazias. |
|
|
39
60
|
| `onChange` | `(answers: AskAnswer[]) => void` | | Recebe a lista completa a cada alteração. |
|
|
40
|
-
| `onSubmit` | `(answers: AskAnswer[]) => void` | |
|
|
41
|
-
| `disabled` | `boolean` | `false` |
|
|
42
|
-
| `busy` | `boolean` | `false` | Envio em andamento: o botão mostra o
|
|
61
|
+
| `onSubmit` | `(answers: AskAnswer[]) => void` | | Chamado somente quando toda pergunta tem uma opção ou um texto livre. |
|
|
62
|
+
| `disabled` | `boolean` | `false` | Bloqueia as opções, o texto livre e o envio. |
|
|
63
|
+
| `busy` | `boolean` | `false` | Envio em andamento: o botão mostra o progresso e os controles ficam bloqueados. |
|
|
64
|
+
| `className` | `string` | | Classes aplicadas à superfície do formulário. |
|
|
65
|
+
|
|
66
|
+
`AskQuestion` e `AskAnswer` são exportados por `@softize/opus`. Cada `AskAnswer` traz `header`,
|
|
67
|
+
`selected` (os rótulos escolhidos) e `text`, quando a pessoa escreveu uma resposta livre.
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
## Vídeo e
|
|
1
|
+
## Vídeo e imagem (16/9)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Use `AspectRatio` para reservar a moldura de uma imagem ou vídeo antes que o conteúdo carregue.
|
|
4
|
+
`ratio` é a razão entre largura e altura; `16 / 9` é o formato comum de vídeo. Defina a largura no
|
|
5
|
+
contêiner pai, e o componente deriva a altura.
|
|
5
6
|
|
|
6
7
|
```tsx preview col
|
|
7
8
|
<div className="w-full max-w-md">
|
|
8
9
|
<AspectRatio ratio={16 / 9} className="overflow-hidden rounded-lg border bg-muted">
|
|
9
10
|
<div className="flex h-full w-full items-center justify-center text-sm text-muted-foreground">
|
|
10
|
-
|
|
11
|
+
Vídeo de apresentação do veículo
|
|
11
12
|
</div>
|
|
12
13
|
</AspectRatio>
|
|
13
14
|
</div>
|
|
@@ -15,42 +16,39 @@ contêiner (o pai): a altura o componente deriva sozinho.
|
|
|
15
16
|
|
|
16
17
|
## Quadrado (1/1)
|
|
17
18
|
|
|
18
|
-
ratio={1}
|
|
19
|
-
|
|
19
|
+
Use `ratio={1}` quando a moldura precisar ser previsível em qualquer largura, como a foto de um
|
|
20
|
+
veículo em uma listagem ou o logotipo de uma marca.
|
|
20
21
|
|
|
21
22
|
```tsx preview col-start
|
|
22
23
|
<div className="w-40">
|
|
23
24
|
<AspectRatio ratio={1} className="overflow-hidden rounded-lg border bg-muted">
|
|
24
|
-
<div className="flex h-full w-full items-center justify-center text-
|
|
25
|
-
|
|
25
|
+
<div className="flex h-full w-full items-center justify-center text-muted-foreground">
|
|
26
|
+
<CarFront className="size-8" />
|
|
26
27
|
</div>
|
|
27
28
|
</AspectRatio>
|
|
28
29
|
</div>
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
## Galeria
|
|
32
|
+
## Galeria
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
Com o mesmo `ratio` em todos os itens, as molduras ficam alinhadas antes mesmo de o conteúdo chegar,
|
|
35
|
+
e a página não pula quando as imagens carregam.
|
|
35
36
|
|
|
36
37
|
```tsx preview col
|
|
37
38
|
<div className="grid w-full grid-cols-3 gap-3">
|
|
38
39
|
{[
|
|
39
|
-
{
|
|
40
|
-
{
|
|
41
|
-
{
|
|
42
|
-
].map((
|
|
40
|
+
{ plate: 'ABC-1D23', model: 'Corolla XEi 2022' },
|
|
41
|
+
{ plate: 'BRA-2E19', model: 'Onix LT 2021' },
|
|
42
|
+
{ plate: 'RIO-4F56', model: 'Compass Longitude 2023' },
|
|
43
|
+
].map((vehicle) => (
|
|
43
44
|
<AspectRatio
|
|
44
|
-
key={
|
|
45
|
+
key={vehicle.plate}
|
|
45
46
|
ratio={16 / 9}
|
|
46
47
|
className="overflow-hidden rounded-lg border bg-muted"
|
|
47
48
|
>
|
|
48
49
|
<div className="flex h-full w-full flex-col justify-end gap-1 p-2">
|
|
49
|
-
<span className="text-sm font-medium">{
|
|
50
|
-
<span className="
|
|
51
|
-
<GitBranch className="size-3" />
|
|
52
|
-
{session.branch}
|
|
53
|
-
</span>
|
|
50
|
+
<span className="text-sm font-medium">{vehicle.plate}</span>
|
|
51
|
+
<span className="text-xs text-muted-foreground">{vehicle.model}</span>
|
|
54
52
|
</div>
|
|
55
53
|
</AspectRatio>
|
|
56
54
|
))}
|
|
@@ -61,6 +59,6 @@ layout quando o preview carrega.
|
|
|
61
59
|
|
|
62
60
|
| Propriedade | Tipo | Padrão | Descrição |
|
|
63
61
|
|---|---|---|---|
|
|
64
|
-
| `ratio` | `number` | `1` |
|
|
65
|
-
| `children` | `
|
|
66
|
-
| `className` | `string` | |
|
|
62
|
+
| `ratio` | `number` | `1` | Razão entre largura e altura: `16 / 9` para vídeo, `1` para quadrado, `4 / 3` para o formato clássico. |
|
|
63
|
+
| `children` | `ReactNode` | | Conteúdo enquadrado, como `img`, `iframe` ou `div`. Preencha a moldura com `h-full w-full` e, em imagens, `object-cover`. |
|
|
64
|
+
| `className` | `string` | | Ajusta a moldura. Borda, raio e `overflow-hidden` ficam aqui, não no filho. |
|
|
@@ -4,9 +4,9 @@ title: Auditoria
|
|
|
4
4
|
|
|
5
5
|
# Auditoria
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
resultado. O runtime emite um `AuditRecord` por execução para
|
|
9
|
-
|
|
7
|
+
Use a auditoria para saber quem executou cada action, quando, com qual entrada e com qual
|
|
8
|
+
resultado. O runtime emite um `AuditRecord` por execução para cada sink configurado, sem código no
|
|
9
|
+
handler. Uma action com `audit: false` fica fora da trilha. O contrato de um sink é o `AuditSink`.
|
|
10
10
|
|
|
11
11
|
## O contrato
|
|
12
12
|
|
|
@@ -43,15 +43,17 @@ interface AuditRecord {
|
|
|
43
43
|
import { consoleAudit } from '@softize/opus/audit/console'
|
|
44
44
|
import { pgAudit } from '@softize/opus/audit/pg'
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// Desenvolvimento: no terminal, cada action gera duas linhas formatadas (a action e seus
|
|
47
|
+
// metadados); fora dele, uma linha JSON por action.
|
|
47
48
|
const dev = consoleAudit()
|
|
48
49
|
|
|
49
50
|
// Produção: grava na tabela audit_log (Postgres).
|
|
50
51
|
const prod = pgAudit({ pool, table: 'audit_log' })
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
adicione colunas próprias e depois habilite
|
|
54
|
+
Por padrão, o `pgAudit` grava as mesmas colunas da tabela legada. Para persistir também o
|
|
55
|
+
`traceContext`, adicione colunas próprias e só depois habilite a opção; não reutilize as colunas
|
|
56
|
+
legadas:
|
|
55
57
|
|
|
56
58
|
```sql
|
|
57
59
|
ALTER TABLE audit_log ADD COLUMN trace_id text;
|
|
@@ -62,14 +64,15 @@ ALTER TABLE audit_log ADD COLUMN span_id text;
|
|
|
62
64
|
const prod = pgAudit({ pool, traceContextColumns: true })
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
Se as colunas já existem com outros nomes, informe-os:
|
|
66
68
|
`traceContextColumns: { traceId: 'otel_trace_id', spanId: 'otel_span_id' }`.
|
|
67
69
|
|
|
68
|
-
##
|
|
70
|
+
## Proteger dados sensíveis
|
|
69
71
|
|
|
70
|
-
Sem redator, os sinks persistem input e output
|
|
71
|
-
gravaria senha em claro. Os dois drivers aceitam `redact
|
|
72
|
-
aplicada a input
|
|
72
|
+
Sem redator, os sinks persistem input e output sem alteração: uma action como `user.create` ou
|
|
73
|
+
`setPassword` gravaria a senha em texto claro. Os dois drivers aceitam `redact`, uma função
|
|
74
|
+
`unknown → unknown` aplicada a input e output antes de gravar ou imprimir, e o Opus oferece um
|
|
75
|
+
redator recursivo:
|
|
73
76
|
|
|
74
77
|
```ts
|
|
75
78
|
import { redactDeep } from '@softize/opus/audit'
|
|
@@ -79,9 +82,9 @@ import { redactDeep } from '@softize/opus/audit'
|
|
|
79
82
|
const prod = pgAudit({ pool, redact: redactDeep })
|
|
80
83
|
```
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
valendo
|
|
85
|
+
Um sink próprio pode reutilizar `redactRecord(record, redator)` e `SENSITIVE_KEY_PATTERN`, do mesmo
|
|
86
|
+
módulo. O `audit: { redact: [...] }` de cada action, com caminhos separados por ponto, continua
|
|
87
|
+
valendo: o redator do sink é uma proteção global adicional, não um substituto.
|
|
85
88
|
|
|
86
89
|
## No runtime
|
|
87
90
|
|
|
@@ -92,10 +95,11 @@ const runtime = createRuntime({
|
|
|
92
95
|
})
|
|
93
96
|
```
|
|
94
97
|
|
|
95
|
-
O que cada action captura se ajusta no contrato
|
|
96
|
-
['senha'], output: false }` refina
|
|
98
|
+
O que cada action captura se ajusta no contrato: `audit: false` desliga a auditoria, e
|
|
99
|
+
`audit: { redact: ['senha'], output: false }` refina o registro. Por padrão, o registro inclui
|
|
100
|
+
input, output e provenance.
|
|
97
101
|
|
|
98
|
-
## Limites
|
|
102
|
+
## Limites atuais
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
`console` e `pg`; outros
|
|
104
|
+
Os sinks só acrescentam registros: consulta e retenção ficam com o seu banco. Os drivers disponíveis
|
|
105
|
+
são `console` e `pg`; outros destinos, como arquivo e OpenTelemetry, ainda não têm driver.
|
|
@@ -4,10 +4,10 @@ title: Autenticação
|
|
|
4
4
|
|
|
5
5
|
# Autenticação
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
handler
|
|
10
|
-
|
|
7
|
+
Use um adapter de autenticação para que cada action saiba quem chama, em qual tenant e com quais
|
|
8
|
+
permissões. A cada requisição, o runtime chama `resolveContext` do `AuthAdapter` e entrega o
|
|
9
|
+
resultado ao handler em `ctx.user`, `ctx.tenantId` e `ctx.can`. O Opus não implementa RBAC nem
|
|
10
|
+
ABAC: a decisão fica com a função `can` fornecida pelo driver.
|
|
11
11
|
|
|
12
12
|
## O contrato
|
|
13
13
|
|
|
@@ -21,7 +21,7 @@ interface AuthAdapter {
|
|
|
21
21
|
}
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
`req` é
|
|
24
|
+
`req` é a requisição original recebida pelo `ServerAdapter`. O `user` vem dos claims ou da sessão, e o driver
|
|
25
25
|
fornece a função `can` que decide cada autorização. O Opus chama essa função sem definir a
|
|
26
26
|
política usada por ela.
|
|
27
27
|
|
|
@@ -47,14 +47,17 @@ const idp = betterAuthSession({
|
|
|
47
47
|
|
|
48
48
|
O `jwt` procura o token no `Authorization: Bearer` e depois no cookie `auth_token` (troque o nome
|
|
49
49
|
com `cookieName` ou a extração inteira com `tokenExtractor`). `mapUser` é obrigatório; o `can`
|
|
50
|
-
|
|
51
|
-
`kid` do token.
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
padrão nega tudo. O `secret` pode ser string, `Buffer` ou uma função assíncrona, chamada sem o
|
|
51
|
+
`kid` do token.
|
|
52
|
+
|
|
53
|
+
O `betterAuthSession` busca a sessão no IdP (`timeoutMs`, padrão de 5 segundos) e a converte em
|
|
54
|
+
`User`, tenant e `can`; o `can` padrão também nega tudo.
|
|
54
55
|
|
|
55
56
|
## No runtime
|
|
56
57
|
|
|
57
58
|
```ts
|
|
59
|
+
import { bindAction, createRuntime } from '@softize/opus/core'
|
|
60
|
+
|
|
58
61
|
const runtime = createRuntime({
|
|
59
62
|
// …server/data…
|
|
60
63
|
auth: idp,
|
|
@@ -67,20 +70,36 @@ export const notaEmitir = bindAction(notaEmitirContract, {
|
|
|
67
70
|
})
|
|
68
71
|
```
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
`
|
|
74
|
-
`
|
|
73
|
+
Antes do handler, o runtime verifica a chamada nesta ordem:
|
|
74
|
+
|
|
75
|
+
1. Valida a entrada pelo schema do contrato.
|
|
76
|
+
2. Sem `public: true`, exige `ctx.user`; sem usuário, responde `auth.unauthenticated`.
|
|
77
|
+
3. Executa os `loads`, que carregam os dados usados na autorização.
|
|
78
|
+
4. Avalia `authorize`, se houver: `false` vira `auth.forbidden`, e um `ActionError` devolvido é
|
|
79
|
+
lançado como está.
|
|
80
|
+
|
|
81
|
+
A autenticação vem antes dos loaders para que um chamador anônimo não dispare consultas nem
|
|
82
|
+
descubra, pelo erro de um loader, se um registro existe. `authorize` aceita uma closure, como
|
|
83
|
+
acima, ou uma expressão avaliada contra `user`, `input` e os dados carregados por `loads`, como
|
|
84
|
+
`'task.ownerId == user.id'`.
|
|
75
85
|
|
|
76
86
|
Uma action sem `authorize` fica aberta a qualquer usuário autenticado. `requires` só documenta a
|
|
77
87
|
permissão esperada: o runtime não o executa, e `opus check` acusa `requires` sem `authorize`.
|
|
78
88
|
Quando a decisão depende de dado que só o handler conhece, verifique no próprio handler com
|
|
79
|
-
`ctx.can` e lance
|
|
80
|
-
|
|
89
|
+
`ctx.can` e lance o mesmo erro que o runtime usaria:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { error } from '@softize/opus/core'
|
|
93
|
+
|
|
94
|
+
if (!(await ctx.can('nota.cancelar', nota))) {
|
|
95
|
+
throw error({ code: 'auth.forbidden', category: 'authorization', message: 'Acesso negado' })
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Sem adapter `auth` configurado, os servidores HTTP resolvem `ctx.user` como `null` e só actions
|
|
81
100
|
`public` executam.
|
|
82
101
|
|
|
83
|
-
## Limites
|
|
102
|
+
## Limites atuais
|
|
84
103
|
|
|
85
|
-
O Opus não modela papéis
|
|
86
|
-
`better-auth
|
|
104
|
+
O Opus não modela papéis nem permissões; isso fica na sua função `can`. Os drivers disponíveis são
|
|
105
|
+
`jwt` e `better-auth`. Outros provedores, como Clerk e Auth.js, ainda não têm driver.
|