@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.
Files changed (151) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +298 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +16 -12
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/heading-level.ts +16 -0
  20. package/src/ui/components/patterns/list.tsx +103 -73
  21. package/src/ui/components/patterns/page.tsx +20 -4
  22. package/src/ui/components/patterns/pane.tsx +294 -0
  23. package/src/ui/components/patterns/presentation.tsx +81 -54
  24. package/src/ui/components/patterns/sidebar.tsx +13 -72
  25. package/src/ui/components/patterns/split.tsx +51 -38
  26. package/src/ui/components/patterns/surface-anatomy.ts +246 -0
  27. package/src/ui/components/patterns/surface-header.tsx +51 -6
  28. package/src/ui/components/patterns/surface-media.tsx +69 -0
  29. package/src/ui/components/patterns/trigger.tsx +12 -2
  30. package/src/ui/components/primitives/alert.tsx +9 -1
  31. package/src/ui/components/primitives/avatar.tsx +7 -3
  32. package/src/ui/components/primitives/card.tsx +91 -29
  33. package/src/ui/components/primitives/carousel.tsx +34 -5
  34. package/src/ui/components/primitives/checkbox.tsx +11 -3
  35. package/src/ui/components/primitives/command.tsx +14 -5
  36. package/src/ui/components/primitives/copyable.tsx +120 -10
  37. package/src/ui/components/primitives/detail.tsx +10 -13
  38. package/src/ui/components/primitives/dialog.tsx +179 -45
  39. package/src/ui/components/primitives/drawer.tsx +131 -24
  40. package/src/ui/components/primitives/empty.tsx +1 -1
  41. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  42. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  43. package/src/ui/components/primitives/input-group.tsx +9 -2
  44. package/src/ui/components/primitives/input-otp.tsx +1 -1
  45. package/src/ui/components/primitives/kbd.tsx +1 -1
  46. package/src/ui/components/primitives/label.tsx +3 -0
  47. package/src/ui/components/primitives/menu.tsx +5 -0
  48. package/src/ui/components/primitives/metric-card.tsx +98 -39
  49. package/src/ui/components/primitives/popover.tsx +1 -1
  50. package/src/ui/components/primitives/progress.tsx +9 -1
  51. package/src/ui/components/primitives/slider.tsx +62 -11
  52. package/src/ui/components/primitives/truncate.tsx +10 -1
  53. package/src/ui/docs/content/accordion.md +74 -27
  54. package/src/ui/docs/content/action-form-card.md +24 -15
  55. package/src/ui/docs/content/action-form-dialog.md +17 -7
  56. package/src/ui/docs/content/action-form.md +92 -77
  57. package/src/ui/docs/content/action-list-dialog.md +75 -38
  58. package/src/ui/docs/content/action-list.md +134 -52
  59. package/src/ui/docs/content/action-trigger.md +27 -10
  60. package/src/ui/docs/content/action-view.md +14 -14
  61. package/src/ui/docs/content/actions.md +161 -66
  62. package/src/ui/docs/content/ai.md +43 -44
  63. package/src/ui/docs/content/alert.md +83 -29
  64. package/src/ui/docs/content/ask.md +51 -26
  65. package/src/ui/docs/content/aspect-ratio.md +22 -24
  66. package/src/ui/docs/content/audit.md +23 -19
  67. package/src/ui/docs/content/auth.md +38 -19
  68. package/src/ui/docs/content/avatar.md +54 -5
  69. package/src/ui/docs/content/badge.md +29 -9
  70. package/src/ui/docs/content/breadcrumb.md +21 -8
  71. package/src/ui/docs/content/button.md +73 -43
  72. package/src/ui/docs/content/calendar.md +134 -26
  73. package/src/ui/docs/content/card.md +252 -35
  74. package/src/ui/docs/content/carousel.md +37 -6
  75. package/src/ui/docs/content/chat.md +118 -37
  76. package/src/ui/docs/content/checkbox.md +73 -7
  77. package/src/ui/docs/content/cli.md +94 -55
  78. package/src/ui/docs/content/collapsible.md +43 -19
  79. package/src/ui/docs/content/command.md +30 -8
  80. package/src/ui/docs/content/composer.md +23 -8
  81. package/src/ui/docs/content/content.md +37 -7
  82. package/src/ui/docs/content/copyable.md +39 -8
  83. package/src/ui/docs/content/customization.md +55 -46
  84. package/src/ui/docs/content/cycle.md +33 -18
  85. package/src/ui/docs/content/data-products.md +115 -0
  86. package/src/ui/docs/content/data-state.md +20 -6
  87. package/src/ui/docs/content/data.md +20 -19
  88. package/src/ui/docs/content/detail.md +12 -5
  89. package/src/ui/docs/content/dialog.md +219 -28
  90. package/src/ui/docs/content/dictionary-value.md +4 -4
  91. package/src/ui/docs/content/dock.md +54 -19
  92. package/src/ui/docs/content/dot.md +35 -5
  93. package/src/ui/docs/content/drawer.md +193 -22
  94. package/src/ui/docs/content/empty-value.md +20 -6
  95. package/src/ui/docs/content/empty.md +24 -3
  96. package/src/ui/docs/content/events.md +76 -16
  97. package/src/ui/docs/content/executions.md +9 -9
  98. package/src/ui/docs/content/field.md +21 -6
  99. package/src/ui/docs/content/getting-started.md +44 -34
  100. package/src/ui/docs/content/icon-picker.md +19 -13
  101. package/src/ui/docs/content/icon-tile.md +28 -5
  102. package/src/ui/docs/content/input-otp.md +58 -7
  103. package/src/ui/docs/content/input.md +22 -3
  104. package/src/ui/docs/content/item.md +53 -6
  105. package/src/ui/docs/content/kbd.md +5 -1
  106. package/src/ui/docs/content/label.md +13 -4
  107. package/src/ui/docs/content/log.md +5 -5
  108. package/src/ui/docs/content/markdown.md +5 -4
  109. package/src/ui/docs/content/mcp.md +49 -23
  110. package/src/ui/docs/content/menu.md +95 -10
  111. package/src/ui/docs/content/metric-card.md +68 -21
  112. package/src/ui/docs/content/observability.md +10 -9
  113. package/src/ui/docs/content/page.md +125 -11
  114. package/src/ui/docs/content/pagination.md +13 -5
  115. package/src/ui/docs/content/pane.md +300 -0
  116. package/src/ui/docs/content/popover.md +42 -5
  117. package/src/ui/docs/content/presentation.md +91 -47
  118. package/src/ui/docs/content/progress.md +27 -14
  119. package/src/ui/docs/content/queue.md +39 -21
  120. package/src/ui/docs/content/radio-group.md +26 -4
  121. package/src/ui/docs/content/router.md +29 -10
  122. package/src/ui/docs/content/runtime.md +90 -54
  123. package/src/ui/docs/content/scheduler.md +53 -21
  124. package/src/ui/docs/content/scroll-area.md +42 -35
  125. package/src/ui/docs/content/select.md +78 -33
  126. package/src/ui/docs/content/semantic-context.md +21 -2
  127. package/src/ui/docs/content/separator.md +15 -9
  128. package/src/ui/docs/content/sidebar.md +25 -45
  129. package/src/ui/docs/content/skeleton.md +13 -4
  130. package/src/ui/docs/content/slider.md +37 -9
  131. package/src/ui/docs/content/spinner.md +4 -3
  132. package/src/ui/docs/content/split.md +34 -20
  133. package/src/ui/docs/content/storage.md +8 -9
  134. package/src/ui/docs/content/switch.md +10 -7
  135. package/src/ui/docs/content/table.md +46 -8
  136. package/src/ui/docs/content/tabs.md +14 -4
  137. package/src/ui/docs/content/testing.md +76 -31
  138. package/src/ui/docs/content/textarea.md +5 -4
  139. package/src/ui/docs/content/toast.md +48 -7
  140. package/src/ui/docs/content/toggle.md +3 -3
  141. package/src/ui/docs/content/tokens.md +15 -26
  142. package/src/ui/docs/content/tooltip.md +18 -2
  143. package/src/ui/docs/content/truncate.md +36 -6
  144. package/src/ui/docs/content/ui.md +74 -22
  145. package/src/ui/docs/content/upgrading.md +45 -36
  146. package/src/ui/docs/plugin.ts +36 -2
  147. package/src/ui/docs/registry.tsx +14 -2
  148. package/src/ui/drivers/react.tsx +16 -8
  149. package/src/ui/meta.ts +38 -32
  150. package/src/ui/react.tsx +14 -0
  151. 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 anuncia o conteúdo com `role="alert"`.
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="Opus 2.8.0" description="Este workspace usa a versão pinada em opus.json." />
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 produtivo e `0.75rem` de padding; não reduza cada uso localmente para obter a
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 como filho também
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. `AlertMedia`,
55
- `AlertHeader` e `AlertActions` são filhos diretos de `Alert`; `AlertTitle` e
56
- `AlertDescription` pertencem ao header. As ações ficam no fim lógico da superfície e centralizadas
57
- verticalmente na mesma linha do conteúdo — a mesma posição usada pelas ações do Toast.
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 novamente, recarregar ou repetir a operação descrita pelo aviso, prefira um
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 deu para publicar</AlertTitle>
84
+ <AlertTitle>Não foi possível publicar</AlertTitle>
71
85
  <AlertDescription>
72
- <p>O registry recusou a versão 3.0.0 — ela já existe.</p>
73
- <p>Suba o patch e tente de novo.</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
- <TooltipProvider>
78
- <Tooltip>
79
- <TooltipTrigger asChild>
80
- <Button variant="ghost" size="icon" aria-label="Tentar novamente">
81
- <RefreshCw />
82
- </Button>
83
- </TooltipTrigger>
84
- <TooltipContent>Tentar novamente</TooltipContent>
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`. Não combine a forma curta com os slots
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 — esse é tooltip nativo, banido na casa, e o componente não o aceita. |
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
- Elicitação estruturada controlada baseada diretamente em `AskQuestion` e `AskAnswer`. O componente só apresenta e valida respostas; transporte, persistência e continuação do agente pertencem ao consumidor.
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] = React.useState([])
11
+ const [answers, setAnswers] = useState<AskAnswer[]>([])
5
12
 
6
13
  render(
7
14
  <Ask
8
- questions={[
9
- {
10
- question: 'Qual ambiente devemos usar?',
11
- header: 'Ambiente',
12
- options: [
13
- { label: 'Produção', description: 'Usa dados e serviços reais.' },
14
- { label: 'Homologação', description: 'Usa o ambiente de validação.' },
15
- ],
16
- },
17
- {
18
- question: 'Quais verificações devo executar?',
19
- header: 'Verificações',
20
- multiSelect: true,
21
- options: [{ label: 'Testes' }, { label: 'Typecheck' }, { label: 'Build' }],
22
- },
23
- ]}
24
- answers={answers}
25
- onChange={setAnswers}
26
- onSubmit={(next) => console.log(next)}
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 pergunta exige pelo menos uma opção válida selecionada **ou** texto livre não vazio. `busy` mostra o progresso no botão e trava os controles; `disabled` apenas trava a interação. A ordem de `answers` acompanha a ordem de `questions`, e o `header` de cada resposta é derivado de `question.header` (ou do próprio enunciado quando o header estiver vazio).
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[]` | | Uma a quatro perguntas do contrato de elicitação; fora dessa faixa o envio é bloqueado. |
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` | | Dispara somente quando toda pergunta tem uma opção ou texto livre. |
41
- | `disabled` | `boolean` | `false` | Trava as opções e o envio. |
42
- | `busy` | `boolean` | `false` | Envio em andamento: o botão mostra o spinner e desabilita. |
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 preview (16/9)
1
+ ## Vídeo e imagem (16/9)
2
2
 
3
- O `ratio` é a proporção entre largura e altura — 16/9 é o formato comum de vídeo. Defina a largura no
4
- contêiner (o pai): a altura o componente deriva sozinho.
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
- Preview do worktree — Empresa X
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} trava em um quadrado — o formato dos avatares de agente e dos ícones de skill, onde a
19
- moldura precisa ser previsível.
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-sm font-medium text-muted-foreground">
25
- developer
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 de previews
32
+ ## Galeria
32
33
 
33
- Mesmo ratio em vários cards: as molduras alinham antes mesmo do conteúdo chegar — sem o pulo de
34
- layout quando o preview carrega.
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
- { ref: 'empresa-x-api', branch: 'feat/preview-pipeline' },
40
- { ref: 'empresa-x-web', branch: 'fix/handoff-modal' },
41
- { ref: 'empresa-x-infra', branch: 'chore/caddy-bump' },
42
- ].map((session) => (
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={session.ref}
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">{session.ref}</span>
50
- <span className="flex items-center gap-1 text-xs text-muted-foreground">
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` | A razão largura/altura. 16/9 para vídeo/preview, 1 para quadrado, 4/3 para clássico. |
65
- | `children` | `React.ReactNode` | | O conteúdo a enquadrar (img, iframe, div) — preencha com h-full w-full e object-cover. |
66
- | `className` | `string` | | Estilo do bloco — borda, rounded e overflow-hidden moram aqui, não no filho. |
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
- Toda action executada vira um registro imutável: quem, o quê, quando, com que entrada e
8
- resultado. O runtime emite um `AuditRecord` por execução para o(s) sink(s) configurado(s) — de
9
- graça, sem o handler pedir. O contrato é o `AuditSink`.
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
- // Dev: uma linha por action (pretty em um TTY, json senão).
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
- O default continua compatível com a tabela legada. Para persistir o contexto novo, primeiro
54
- adicione colunas próprias e depois habilite o opt-in — não reutilize as colunas legadas:
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
- Rollouts com nomes existentes podem usar
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
- ## Dado sensível: o redator global
70
+ ## Proteger dados sensíveis
69
71
 
70
- Sem redator, os sinks persistem input e output **crus** — `user.create`/`setPassword`
71
- gravaria senha em claro. Os dois drivers aceitam `redact` (função `unknown → unknown`
72
- aplicada a input/output antes de persistir/imprimir), e a base traz o redator recursivo:
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
- Sink próprio reusa as peças: `redactRecord(record, redator)` e `SENSITIVE_KEY_PATTERN`
83
- saem do mesmo módulo. O `audit: { redact: [...] }` por action (dot-paths) continua
84
- valendo — o redator do sink é o cinto de segurança global, não o substituto.
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 (`audit: false` desliga; `audit: { redact:
96
- ['senha'], output: false }` refina). O default captura tudo — input, output e provenance.
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 (por enquanto)
102
+ ## Limites atuais
99
103
 
100
- Sink append-only (sem query/retenção embutida — isso é do seu banco). Drivers hoje:
101
- `console` e `pg`; outros (arquivo, OpenTelemetry…) entram por reincidência.
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
- Quem é o usuário, de qual tenant, e o que ele pode. O contrato é um adapter do core
8
- (`AuthAdapter`): a cada request o runtime chama `resolveContext` e injeta o resultado no
9
- handler — `ctx.user`, `ctx.tenantId`, `ctx.can`. O Opus **não** implementa RBAC/ABAC; ele
10
- delega a decisão para o `can` que o driver pluga.
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` é o request cru do `ServerAdapter`. O `user` vem dos claims ou da sessão, e o driver
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
- default nega tudo. O `secret` pode ser string, `Buffer` ou um resolver async, hoje chamado sem o
51
- `kid` do token. O `betterAuthSession` faz fetch da
52
- sessão no IdP (`timeoutMs`, default 5s) e mapeia para o `User`/tenant/can (o `can` default nega
53
- tudo — plugue o seu).
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
- O runtime aplica duas barreiras antes do handler. Sem `public: true`, a action exige
71
- `ctx.user`; sem usuário, responde `auth.unauthenticated`. Depois, se houver `authorize`, o
72
- runtime o avalia: `false` vira `auth.forbidden` e um `ActionError` devolvido é lançado como está.
73
- `authorize` aceita uma closure, como acima, ou uma expressão da DSL avaliada contra `user`,
74
- `input` e os dados carregados por `loads`.
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 `error({ code: 'forbidden', category: 'authorization' })`.
80
- Sem adapter `auth` configurado, os servidores HTTP resolvem `ctx.user` como null e só actions
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 (por enquanto)
102
+ ## Limites atuais
84
103
 
85
- O Opus não modela papéis/permissões — isso é do seu `can`. Drivers hoje: `jwt` e
86
- `better-auth`; outros (Clerk, Auth.js…) entram por reincidência.
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.