@softize/opus 12.11.0 → 13.1.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 +76 -0
- package/PROMOTED.md +46 -0
- package/README.md +28 -19
- package/bin/cli.mjs +87 -216
- package/bin/lib/check.mjs +2 -7
- package/bin/lib/cli-shared.mjs +131 -0
- package/bin/lib/copy.mjs +1 -5
- package/bin/lib/db.mjs +16 -74
- package/bin/lib/gen-openapi.mjs +3 -3
- package/bin/lib/gen-runner.mjs +1 -1
- package/bin/lib/gen.mjs +14 -69
- package/bin/lib/mcp.mjs +3 -1
- package/bin/lib/seed.mjs +5 -62
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +93 -10
- package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
- package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
- package/docs/ownership-vs-shadcn-lock.md +2 -3
- package/docs/protocol.md +7 -7
- package/docs/radius-scale.md +1 -1
- package/docs/releasing.md +8 -2
- package/package.json +7 -3
- 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/templates/app/package.json +1 -1
- package/registry/templates/app/src/main.tsx +4 -4
- package/src/audit/drivers/console.ts +1 -0
- package/src/auth/drivers/better-auth.ts +1 -0
- package/src/auth/drivers/jwt.ts +1 -0
- package/src/cache/drivers/memory.ts +1 -0
- package/src/client/drivers/fetch.ts +2 -1
- package/src/core/actions.ts +6 -1
- package/src/core/audit.ts +9 -3
- package/src/core/contracts.ts +7 -0
- package/src/core/domain.ts +1 -1
- package/src/core/errors.ts +18 -15
- package/src/core/index.ts +4 -2
- package/src/core/package-version.ts +26 -0
- package/src/core/reactions.ts +1 -1
- package/src/core/runtime.ts +33 -23
- package/src/core/schedules.ts +1 -1
- package/src/core/types.ts +2 -2
- package/src/dsl/eval.ts +2 -2
- package/src/dsl/kysely.ts +2 -2
- package/src/dsl/loads.ts +1 -1
- package/src/dsl/parser.ts +5 -5
- package/src/events/drivers/mitt.ts +1 -0
- package/src/mcp/index.ts +2 -1
- package/src/observability/drivers/opentelemetry.ts +1 -0
- package/src/queue/drivers/bullmq.ts +3 -3
- package/src/scheduler/drivers/node-cron.ts +3 -2
- package/src/scheduler/every.ts +7 -7
- package/src/schema/openapi.ts +3 -3
- package/src/seed/index.ts +29 -0
- package/src/server/drivers/fastify.ts +5 -2
- package/src/server/drivers/node.ts +9 -6
- package/src/server/index.ts +3 -1
- package/src/storage/drivers/fs.ts +1 -0
- package/src/testing/index.ts +3 -3
- package/src/ui/components/patterns/confirm.tsx +142 -42
- package/src/ui/components/patterns/content-header.tsx +7 -1
- package/src/ui/components/patterns/data-state.tsx +1 -1
- package/src/ui/components/patterns/dock.tsx +20 -3
- package/src/ui/components/patterns/form.tsx +12 -8
- package/src/ui/components/patterns/list.tsx +36 -41
- package/src/ui/components/patterns/page-state.tsx +2 -2
- package/src/ui/components/patterns/page.tsx +19 -1
- package/src/ui/components/patterns/shell-nav.tsx +10 -3
- package/src/ui/components/patterns/sidebar.tsx +43 -32
- package/src/ui/components/patterns/trigger.tsx +39 -38
- package/src/ui/components/patterns/view.tsx +26 -17
- package/src/ui/components/primitives/alert.tsx +14 -8
- package/src/ui/components/primitives/ask.tsx +3 -3
- package/src/ui/components/primitives/badge.tsx +11 -6
- package/src/ui/components/primitives/breadcrumb.tsx +2 -2
- package/src/ui/components/primitives/button.tsx +16 -3
- package/src/ui/components/primitives/calendar.tsx +28 -2
- package/src/ui/components/primitives/carousel.tsx +3 -3
- package/src/ui/components/primitives/chat.tsx +1 -1
- package/src/ui/components/primitives/checkbox.tsx +1 -1
- package/src/ui/components/primitives/command.tsx +2 -2
- package/src/ui/components/primitives/control.ts +12 -0
- package/src/ui/components/primitives/copyable.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +202 -40
- package/src/ui/components/primitives/dot.tsx +5 -0
- package/src/ui/components/primitives/drawer.tsx +18 -8
- package/src/ui/components/primitives/empty.tsx +3 -3
- package/src/ui/components/primitives/field.tsx +3 -3
- package/src/ui/components/primitives/icon-picker.tsx +3 -1
- package/src/ui/components/primitives/input-group.tsx +1 -1
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/input.tsx +2 -2
- package/src/ui/components/primitives/item.tsx +3 -3
- package/src/ui/components/primitives/progress.tsx +32 -3
- package/src/ui/components/primitives/radio-group.tsx +1 -1
- package/src/ui/components/primitives/resizable.tsx +3 -1
- package/src/ui/components/primitives/select.tsx +5 -5
- package/src/ui/components/primitives/slider.tsx +5 -1
- package/src/ui/components/primitives/sonner.tsx +190 -8
- package/src/ui/components/primitives/switch.tsx +1 -0
- package/src/ui/components/primitives/tabs.tsx +1 -0
- package/src/ui/components/primitives/textarea.tsx +1 -1
- package/src/ui/components/primitives/toggle.tsx +1 -1
- package/src/ui/components/primitives/tooltip.tsx +1 -0
- package/src/ui/docs/DocBrowser.tsx +102 -23
- 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 +37 -36
- package/src/ui/docs/content/action-list-dialog.md +11 -6
- package/src/ui/docs/content/action-list.md +73 -39
- package/src/ui/docs/content/action-trigger.md +29 -15
- package/src/ui/docs/content/action-view.md +17 -10
- package/src/ui/docs/content/actions.md +9 -9
- package/src/ui/docs/content/ai.md +3 -3
- package/src/ui/docs/content/alert.md +14 -12
- package/src/ui/docs/content/ask.md +11 -0
- package/src/ui/docs/content/aspect-ratio.md +4 -4
- package/src/ui/docs/content/audit.md +2 -2
- package/src/ui/docs/content/auth.md +3 -3
- package/src/ui/docs/content/avatar.md +34 -14
- package/src/ui/docs/content/badge.md +3 -3
- package/src/ui/docs/content/breadcrumb.md +13 -8
- package/src/ui/docs/content/button.md +81 -6
- package/src/ui/docs/content/calendar.md +18 -5
- package/src/ui/docs/content/card.md +27 -1
- package/src/ui/docs/content/carousel.md +16 -11
- package/src/ui/docs/content/chat.md +23 -3
- package/src/ui/docs/content/checkbox.md +7 -7
- package/src/ui/docs/content/cli.md +74 -22
- 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 +17 -2
- package/src/ui/docs/content/content.md +17 -2
- package/src/ui/docs/content/copyable.md +12 -3
- package/src/ui/docs/content/customization.md +5 -5
- package/src/ui/docs/content/cycle.md +3 -3
- package/src/ui/docs/content/data-state.md +11 -12
- package/src/ui/docs/content/data.md +26 -33
- package/src/ui/docs/content/detail.md +22 -4
- package/src/ui/docs/content/dialog.md +339 -31
- package/src/ui/docs/content/dictionary-value.md +17 -10
- package/src/ui/docs/content/dock.md +11 -3
- package/src/ui/docs/content/dot.md +8 -0
- package/src/ui/docs/content/drawer.md +27 -14
- package/src/ui/docs/content/empty-value.md +2 -2
- package/src/ui/docs/content/empty.md +19 -12
- package/src/ui/docs/content/events.md +4 -4
- package/src/ui/docs/content/field.md +34 -12
- package/src/ui/docs/content/getting-started.md +3 -3
- package/src/ui/docs/content/icon-picker.md +19 -4
- package/src/ui/docs/content/input-otp.md +20 -12
- package/src/ui/docs/content/input.md +121 -9
- package/src/ui/docs/content/item.md +27 -13
- package/src/ui/docs/content/kbd.md +19 -11
- package/src/ui/docs/content/label.md +12 -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 +40 -16
- package/src/ui/docs/content/metric-card.md +13 -0
- package/src/ui/docs/content/observability.md +2 -2
- package/src/ui/docs/content/page.md +59 -6
- package/src/ui/docs/content/pagination.md +22 -17
- package/src/ui/docs/content/popover.md +22 -8
- package/src/ui/docs/content/progress.md +15 -16
- 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 +47 -34
- package/src/ui/docs/content/semantic-context.md +2 -2
- package/src/ui/docs/content/separator.md +5 -5
- package/src/ui/docs/content/sidebar.md +329 -54
- package/src/ui/docs/content/skeleton.md +9 -2
- package/src/ui/docs/content/slider.md +8 -7
- package/src/ui/docs/content/spinner.md +8 -8
- package/src/ui/docs/content/split.md +29 -5
- package/src/ui/docs/content/storage.md +6 -8
- package/src/ui/docs/content/switch.md +8 -7
- package/src/ui/docs/content/table.md +13 -3
- package/src/ui/docs/content/tabs.md +28 -14
- package/src/ui/docs/content/testing.md +9 -11
- package/src/ui/docs/content/textarea.md +12 -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 +7 -7
- package/src/ui/docs/content/tooltip.md +19 -11
- package/src/ui/docs/content/truncate.md +15 -8
- package/src/ui/docs/content/ui.md +24 -9
- package/src/ui/docs/content/upgrading.md +7 -8
- package/src/ui/docs/doc-client.tsx +5 -5
- package/src/ui/docs/doc.tsx +26 -14
- package/src/ui/docs/registry.tsx +25 -42
- package/src/ui/docs/standalone.tsx +2 -2
- package/src/ui/drivers/react.tsx +17 -12
- package/src/ui/lib/action-errors.ts +45 -0
- package/src/ui/lib/zod-pt-br.ts +31 -4
- package/src/ui/meta.ts +65 -95
- package/src/ui/react.tsx +17 -16
- package/src/ui/theme.css +60 -8
- package/src/vite/design.ts +6 -18
- package/src/ui/components/primitives/alert-dialog.tsx +0 -192
- package/src/ui/docs/content/alert-dialog.md +0 -73
- package/src/ui/docs/content/button-group.md +0 -71
- package/src/ui/docs/content/confirm.md +0 -120
- package/src/ui/docs/content/input-group.md +0 -79
- package/src/ui/docs/content/page-state.md +0 -45
- package/src/ui/docs/content/toggle-group.md +0 -81
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Uma tecla
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use um `Kbd` para representar cada tecla. O componente apresenta o atalho; a captura do teclado
|
|
4
|
+
continua no handler do aplicativo.
|
|
4
5
|
|
|
5
6
|
```tsx preview
|
|
6
7
|
<Kbd>Esc</Kbd>
|
|
@@ -8,7 +9,7 @@ Uma tecla por Kbd. É só visual — quem escuta o atalho é o seu handler, não
|
|
|
8
9
|
|
|
9
10
|
## Combinação
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Agrupe as teclas com `KbdGroup` e use `+` como texto entre elas.
|
|
12
13
|
|
|
13
14
|
```tsx preview
|
|
14
15
|
<KbdGroup>
|
|
@@ -20,7 +21,8 @@ Envolva as teclas num KbdGroup e ponha o conector (+) como texto entre elas —
|
|
|
20
21
|
|
|
21
22
|
## Com ícone
|
|
22
23
|
|
|
23
|
-
Um ícone
|
|
24
|
+
Um ícone Lucide recebe automaticamente a escala do componente. Use-o quando o símbolo for mais
|
|
25
|
+
reconhecível que o nome da tecla.
|
|
24
26
|
|
|
25
27
|
```tsx preview col-start
|
|
26
28
|
<KbdGroup>
|
|
@@ -35,7 +37,8 @@ Um ícone (lucide) como filho do Kbd ganha size-3 automático — bom pra ⌘ e
|
|
|
35
37
|
|
|
36
38
|
## No tooltip
|
|
37
39
|
|
|
38
|
-
Dentro de
|
|
40
|
+
Dentro de `TooltipContent`, o componente ajusta o contraste automaticamente. O tooltip deve nomear a
|
|
41
|
+
ação antes de informar seu atalho.
|
|
39
42
|
|
|
40
43
|
```tsx preview
|
|
41
44
|
<Tooltip>
|
|
@@ -52,11 +55,16 @@ Dentro de um TooltipContent o Kbd inverte o tom sozinho (fundo claro sobre o bal
|
|
|
52
55
|
</Tooltip>
|
|
53
56
|
```
|
|
54
57
|
|
|
55
|
-
##
|
|
58
|
+
## Propriedades de Kbd
|
|
56
59
|
|
|
57
|
-
|
|
|
60
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
58
61
|
|---|---|---|---|
|
|
59
|
-
| `children
|
|
60
|
-
| `className
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
| `children` | `React.ReactNode` | | Texto ou ícone que representa a tecla. Ícones Lucide recebem a escala visual do componente. |
|
|
63
|
+
| `className` | `string` | | Classes adicionais aplicadas à tecla. |
|
|
64
|
+
|
|
65
|
+
## Propriedades de KbdGroup
|
|
66
|
+
|
|
67
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `children` | `React.ReactNode` | | Teclas da combinação e conectores, como `+`, entre elas. |
|
|
70
|
+
| `className` | `string` | | Classes adicionais aplicadas ao agrupador. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Com campo de texto
|
|
2
2
|
|
|
3
|
-
htmlFor
|
|
3
|
+
Associe `htmlFor` ao `id` do controle para que clicar no rótulo mova o foco para o campo.
|
|
4
4
|
|
|
5
5
|
```tsx preview col md
|
|
6
6
|
<div className="grid gap-2">
|
|
@@ -11,7 +11,8 @@ htmlFor aponta pro id do controle — clicar no rótulo foca o campo.
|
|
|
11
11
|
|
|
12
12
|
## Com Checkbox
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
`Label` já organiza controles inline com espaçamento. Ao associá-lo ao `Checkbox`, clicar no texto
|
|
15
|
+
também alterna a seleção.
|
|
15
16
|
|
|
16
17
|
```tsx preview
|
|
17
18
|
<div className="flex items-center gap-2">
|
|
@@ -22,7 +23,8 @@ O Label já é flex com gap — controle inline entra sem wrapper extra; clicar
|
|
|
22
23
|
|
|
23
24
|
## Par desabilitado
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
Quando o controle está desabilitado, o rótulo associado acompanha o tratamento visual sem exigir
|
|
27
|
+
uma classe adicional.
|
|
26
28
|
|
|
27
29
|
```tsx preview
|
|
28
30
|
<div className="flex items-center gap-2">
|
|
@@ -30,3 +32,10 @@ Controle disabled esmaece o rótulo junto (peer-disabled) — o par inteiro comu
|
|
|
30
32
|
<Label htmlFor="archive-workspace">Arquivar o workspace</Label>
|
|
31
33
|
</div>
|
|
32
34
|
```
|
|
35
|
+
|
|
36
|
+
## Propriedades de Label
|
|
37
|
+
|
|
38
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `htmlFor` | `string` | | O `id` do controle rotulado; clicar no rótulo foca o controle. |
|
|
41
|
+
| `className` | `string` | | Compõe sobre o estilo padrão; o rótulo esmaece com `peer-disabled` e dentro de `group[data-disabled]`. |
|
|
@@ -4,9 +4,9 @@ title: Log
|
|
|
4
4
|
|
|
5
5
|
# Log
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
`
|
|
9
|
-
|
|
7
|
+
Use `ctx.log` para registrar eventos estruturados com o contexto da requisição e da action. O
|
|
8
|
+
`LoggerAdapter` também recebe os registros internos do runtime, mantendo aplicação e Opus no mesmo
|
|
9
|
+
destino e formato.
|
|
10
10
|
|
|
11
11
|
## O contrato
|
|
12
12
|
|
|
@@ -49,7 +49,7 @@ handler: async (ctx, input) => {
|
|
|
49
49
|
}
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
## Limites
|
|
52
|
+
## Limites atuais
|
|
53
53
|
|
|
54
54
|
Superfície mínima de níveis + `child`. Sem sampling nem transports próprios — isso mora no
|
|
55
55
|
`pino` que você passa. Drivers hoje: `pino`; outros entram por reincidência.
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
## Fonte e resultado
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Passe o texto em `content`. O exemplo mostra primeiro o resultado renderizado e, depois, o Markdown
|
|
4
|
+
que o produziu.
|
|
4
5
|
|
|
5
6
|
```tsx preview col 2xl
|
|
6
7
|
const sample = [
|
|
7
8
|
'# Markdown na Softize',
|
|
8
9
|
'',
|
|
9
10
|
'Renderiza **doc técnica** e mensagem de chat — `SKILL.md`, síntese de papel, resposta de agente.',
|
|
10
|
-
'O motor é o markdown-it, o
|
|
11
|
+
'O motor é o markdown-it, o mesmo que renderiza estas páginas.',
|
|
11
12
|
'',
|
|
12
13
|
'## Sintaxe coberta',
|
|
13
14
|
'',
|
|
@@ -18,7 +19,7 @@ const sample = [
|
|
|
18
19
|
'1. Primeiro passo',
|
|
19
20
|
'2. Segundo passo',
|
|
20
21
|
'',
|
|
21
|
-
'> Citação calma,
|
|
22
|
+
'> Citação calma, para o tom certo.',
|
|
22
23
|
'',
|
|
23
24
|
'---',
|
|
24
25
|
'',
|
|
@@ -33,9 +34,9 @@ render(<Markdown content={sample} />)
|
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
##
|
|
37
|
+
## Propriedades de Markdown
|
|
37
38
|
|
|
38
|
-
|
|
|
39
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
39
40
|
|---|---|---|---|
|
|
40
|
-
| `content` | `string` | | O markdown cru (CommonMark + tabela GFM, via markdown-it). HTML no fonte é **escapado**, não interpretado — por isso serve
|
|
41
|
+
| `content` | `string` | | O markdown cru (CommonMark + tabela GFM, via markdown-it). HTML no fonte é **escapado**, não interpretado — por isso serve para texto vindo de gente ou de modelo. |
|
|
41
42
|
| `className` | `string` | | Classes do wrapper. |
|
|
@@ -4,10 +4,9 @@ title: MCP
|
|
|
4
4
|
|
|
5
5
|
# MCP
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
como o usuário resolvido). O MCP é só o transporte.
|
|
7
|
+
Use o servidor MCP para disponibilizar actions `ai:enabled` a agentes que executam fora da
|
|
8
|
+
aplicação. A chamada continua passando por `runtime.execute`, com a mesma validação, autorização e
|
|
9
|
+
auditoria; o MCP fornece apenas o transporte.
|
|
11
10
|
|
|
12
11
|
## Montar
|
|
13
12
|
|
|
@@ -22,23 +21,22 @@ const server = createOpusMcpServer(runtime, {
|
|
|
22
21
|
resolveContext: async (extra) => auth.resolveFromMcp(extra),
|
|
23
22
|
})
|
|
24
23
|
|
|
25
|
-
await server.connect(new StdioServerTransport()) // local; HTTP/SSE
|
|
24
|
+
await server.connect(new StdioServerTransport()) // local; HTTP/SSE para remoto
|
|
26
25
|
```
|
|
27
26
|
|
|
28
27
|
`ListTools` devolve as actions `ai:enabled` (nome + descrição + JSON Schema do input);
|
|
29
28
|
`CallTool` executa a action pelo `runtime.execute` — validação, auth (`ctx.can`) e audit,
|
|
30
29
|
tudo igual a uma chamada normal. Read-only? Marque só actions de leitura com `ai:enabled`.
|
|
31
30
|
|
|
32
|
-
##
|
|
31
|
+
## Escolher entre integração interna e MCP
|
|
33
32
|
|
|
34
|
-
- **
|
|
35
|
-
|
|
36
|
-
- **
|
|
37
|
-
|
|
33
|
+
- **Agente dentro da aplicação:** use `runtime.aiFor(base).run()` para chamar as actions no mesmo
|
|
34
|
+
processo.
|
|
35
|
+
- **Agente em outro serviço:** use MCP para oferecer a mesma coleção de ferramentas por uma
|
|
36
|
+
interface interoperável.
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
## Limites atuais
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
`destructive` sobre MCP (elicitation) entra quando um caso real cobrar; comece read-only.
|
|
40
|
+
O consumidor escolhe e monta o transporte, como stdio ou HTTP. A confirmação de actions destrutivas
|
|
41
|
+
ainda não faz parte desta integração; até que esse fluxo exista, exponha somente operações de
|
|
42
|
+
leitura.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
## Menu de ações
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`context="danger"`
|
|
3
|
+
Use `Menu` para reunir ações relacionadas em um painel ancorado. `MenuShortcut` posiciona o atalho à
|
|
4
|
+
direita; separe ações perigosas e declare `context="danger"` nelas.
|
|
5
5
|
|
|
6
6
|
```tsx preview
|
|
7
7
|
<Menu>
|
|
@@ -17,11 +17,10 @@
|
|
|
17
17
|
</Menu>
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Abrir pelo menu de contexto
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
um gatilho invisível `position: fixed` naquele ponto ancora o conteúdo.
|
|
22
|
+
Para abrir pelo clique direito, controle o estado do menu e use a posição do ponteiro como âncora.
|
|
23
|
+
Esse padrão substitui o antigo `ContextMenu` sem introduzir outra família de componentes.
|
|
25
24
|
|
|
26
25
|
```tsx preview
|
|
27
26
|
const [pos, setPos] = useState(null)
|
|
@@ -52,9 +51,10 @@ render(
|
|
|
52
51
|
)
|
|
53
52
|
```
|
|
54
53
|
|
|
55
|
-
## Seleção
|
|
54
|
+
## Seleção por checkbox ou radio
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
Use `MenuCheckboxItem` para opções independentes e `MenuRadioGroup` para escolhas mutuamente
|
|
57
|
+
exclusivas. O consumidor controla o estado nos dois casos.
|
|
58
58
|
|
|
59
59
|
```tsx preview
|
|
60
60
|
const [showArchived, setShowArchived] = useState(false)
|
|
@@ -93,7 +93,7 @@ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
|
|
|
93
93
|
<MenuContent align="start">
|
|
94
94
|
<MenuItem><ExternalLink /> Abrir preview</MenuItem>
|
|
95
95
|
<MenuSub>
|
|
96
|
-
<MenuSubTrigger><ArrowDownAZ /> Mover
|
|
96
|
+
<MenuSubTrigger><ArrowDownAZ /> Mover para</MenuSubTrigger>
|
|
97
97
|
<MenuSubContent>
|
|
98
98
|
<MenuItem>Empresa X</MenuItem>
|
|
99
99
|
<MenuItem>Softize</MenuItem>
|
|
@@ -104,12 +104,36 @@ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
|
|
|
104
104
|
</Menu>
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
##
|
|
107
|
+
## Propriedades de MenuItem
|
|
108
108
|
|
|
109
|
-
|
|
|
109
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
110
110
|
|---|---|---|---|
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
| `context` | `'neutral' \| 'danger'` | `'neutral'` | `danger` sinaliza uma ação com consequência perigosa. |
|
|
112
|
+
| `inset` | `boolean` | | Alinha um item sem ícone com os itens que possuem ícone. |
|
|
113
|
+
|
|
114
|
+
## Propriedades de MenuGroup
|
|
115
|
+
|
|
116
|
+
`MenuGroup` agrupa itens relacionados para leitores de tela (`role="group"`); combine com
|
|
117
|
+
`MenuLabel` para nomear o grupo e `MenuSeparator` para separá-lo do próximo. Não tem props
|
|
118
|
+
próprias além das de DOM.
|
|
119
|
+
|
|
120
|
+
## Propriedades de MenuContent
|
|
121
|
+
|
|
122
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| `align` | `'start' \| 'center' \| 'end'` | `'center'` | Alinhamento do painel em relação ao gatilho. |
|
|
125
|
+
| `sideOffset` | `number` | `4` | Distância entre o gatilho e o painel. |
|
|
126
|
+
|
|
127
|
+
## Propriedades de MenuCheckboxItem
|
|
128
|
+
|
|
129
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| `checked` | `boolean` | | Estado controlado do item. |
|
|
132
|
+
| `onCheckedChange` | `(checked: boolean) => void` | | Chamado quando a pessoa alterna o item. |
|
|
133
|
+
|
|
134
|
+
## Propriedades de MenuRadioGroup
|
|
135
|
+
|
|
136
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
137
|
+
|---|---|---|---|
|
|
138
|
+
| `value` | `string` | | Valor selecionado no grupo. |
|
|
139
|
+
| `onValueChange` | `(value: string) => void` | | Chamado quando a pessoa seleciona outro `MenuRadioItem`. |
|
|
@@ -39,3 +39,16 @@ tecnologias assistivas. O contêiner da coleção continua responsável pela men
|
|
|
39
39
|
```tsx preview col
|
|
40
40
|
<MetricCard loading />
|
|
41
41
|
```
|
|
42
|
+
|
|
43
|
+
## Propriedades de MetricCard
|
|
44
|
+
|
|
45
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| `label` | `ReactNode` | | O nome da medida. |
|
|
48
|
+
| `value` | `ReactNode` | | O valor já formatado pelo consumidor. |
|
|
49
|
+
| `loading` | `boolean` | `false` | Substitui rótulo e valor por skeletons e marca `aria-busy`. |
|
|
50
|
+
| `labelAction` | `ReactNode` | | Controle ao lado do rótulo, como uma explicação em tooltip. |
|
|
51
|
+
| `description` | `ReactNode` | | Contexto que explica recorte, proporção ou significado do valor. |
|
|
52
|
+
| `icon` | `ReactNode` | | Ícone decorativo que identifica a natureza da medida. |
|
|
53
|
+
| `context` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Contexto semântico do ícone; não altera a superfície do card. |
|
|
54
|
+
| `action` | `ReactNode` | | Ação relacionada diretamente à medida. |
|
|
@@ -4,8 +4,8 @@ title: Observabilidade
|
|
|
4
4
|
|
|
5
5
|
# Observabilidade
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Use `ObservabilityAdapter` para envolver actions e reactions em spans sem acoplar o core a um
|
|
8
|
+
fornecedor. O driver OpenTelemetry deixa backend, exporter e Collector sob controle do aplicativo.
|
|
9
9
|
|
|
10
10
|
## Driver OpenTelemetry
|
|
11
11
|
|
|
@@ -16,10 +16,9 @@ Uma navegação contextual para outra página pode ocupar `actions`, e tabs fica
|
|
|
16
16
|
recortes da mesma superfície. Canvas e outros workspaces espaciais imersivos podem usar um shell
|
|
17
17
|
próprio quando o cabeçalho reduzir a área útil ou duplicar controles persistentes da superfície.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
fontes independentes e uma falha parcial não deve ocultar as demais seções.
|
|
19
|
+
Estados integrais de carregamento, falha ou ausência são compostos no body com `PageState`,
|
|
20
|
+
detalhado abaixo. `Page` não recebe flags de dados: uma página pode agregar fontes independentes e
|
|
21
|
+
uma falha parcial não deve ocultar as demais seções.
|
|
23
22
|
|
|
24
23
|
```tsx preview col
|
|
25
24
|
render(
|
|
@@ -42,6 +41,14 @@ render(
|
|
|
42
41
|
)
|
|
43
42
|
```
|
|
44
43
|
|
|
44
|
+
## Ações no chrome do shell
|
|
45
|
+
|
|
46
|
+
Quando o shell reserva uma barra própria para contexto e ações, envolva sua região de conteúdo com
|
|
47
|
+
`PageActionsTarget` e passe o elemento de destino em `target`. As ações declaradas em `Page`
|
|
48
|
+
continuam pertencendo semanticamente ao cabeçalho da página, mas são projetadas nesse elemento.
|
|
49
|
+
Com `target={null}`, elas permanecem na posição padrão; isso permite montar o alvo por `ref` sem
|
|
50
|
+
uma renderização intermediária inconsistente.
|
|
51
|
+
|
|
45
52
|
## Composição explícita
|
|
46
53
|
|
|
47
54
|
Use os slots quando a página precisar compor o header diretamente. Não misture propriedades da
|
|
@@ -65,9 +72,44 @@ render(
|
|
|
65
72
|
)
|
|
66
73
|
```
|
|
67
74
|
|
|
68
|
-
##
|
|
75
|
+
## Estados integrais
|
|
76
|
+
|
|
77
|
+
Use `PageState` quando carregamento, falha ou ausência substituírem todo o conteúdo principal. Na
|
|
78
|
+
forma curta ele pode ser escrito como filho direto de `Page`, que cria o `PageBody`; na forma
|
|
79
|
+
explícita, coloque-o dentro de `PageBody`. O cabeçalho continua visível e o estado recebe composição,
|
|
80
|
+
altura e semântica acessível consistentes.
|
|
81
|
+
|
|
82
|
+
```tsx preview col
|
|
83
|
+
<Page title="Relatório">
|
|
84
|
+
<PageState
|
|
85
|
+
status="error"
|
|
86
|
+
title="Não foi possível carregar o relatório"
|
|
87
|
+
description="Tente novamente. Se o problema continuar, volte mais tarde."
|
|
88
|
+
action={<Button>Tentar novamente</Button>}
|
|
89
|
+
/>
|
|
90
|
+
</Page>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`loading` centraliza o `Spinner`; `error` compõe `Alert`; `empty` compõe `Empty`; e `ready` entrega
|
|
94
|
+
os filhos sem acrescentar uma superfície.
|
|
69
95
|
|
|
70
|
-
|
|
96
|
+
```tsx preview col
|
|
97
|
+
<Page title="Relatórios">
|
|
98
|
+
<PageState
|
|
99
|
+
status="empty"
|
|
100
|
+
title="Nenhum relatório"
|
|
101
|
+
description="Crie o primeiro relatório para começar."
|
|
102
|
+
action={<Button>Novo relatório</Button>}
|
|
103
|
+
/>
|
|
104
|
+
</Page>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Não use `PageState` para uma falha parcial. Se outra parte da página continua utilizável, mantenha o
|
|
108
|
+
estado na própria seção com `DataState`, `ActionView`, `ActionList` ou `Alert`.
|
|
109
|
+
|
|
110
|
+
## Propriedades de Page
|
|
111
|
+
|
|
112
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
71
113
|
| ------------- | ----------- | ----------- | ----------------------------------------------------------------------------------------------- |
|
|
72
114
|
| `title` | `string` | | O h1 da página. |
|
|
73
115
|
| `count` | `number` | | Total de itens ao lado do título (mono, esmaecido). |
|
|
@@ -75,3 +117,14 @@ render(
|
|
|
75
117
|
| `actions` | `ReactNode` | | Ações contextuais no extremo oposto do cabeçalho; em telas estreitas, ficam abaixo do contexto. |
|
|
76
118
|
| `className` | `string` | `max-w-7xl` | Classes do container para substituir o teto padrão de `80rem`. |
|
|
77
119
|
| `children` | `ReactNode` | | O body da página — espaçamento e diagramação são seus. |
|
|
120
|
+
|
|
121
|
+
## Propriedades de PageState
|
|
122
|
+
|
|
123
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
124
|
+
|---|---|---|---|
|
|
125
|
+
| `status` | `'loading' \| 'error' \| 'empty' \| 'ready'` | | Estado integral do conteúdo. |
|
|
126
|
+
| `title` | `ReactNode` | Texto seguro por estado | Situação reconhecível pela pessoa. |
|
|
127
|
+
| `description` | `ReactNode` | | Impacto ou próximo passo aplicável. |
|
|
128
|
+
| `icon` | `ReactNode` | Alerta no erro | Ícone decorativo do estado. |
|
|
129
|
+
| `action` | `ReactNode` | | Recuperação, seleção ou criação aplicável. |
|
|
130
|
+
| `children` | `ReactNode` | | Conteúdo renderizado somente em `ready`. |
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Navegação entre páginas
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
É o único paginador da casa: `ActionList` compõe esta primitiva no rodapé, na escala densa.
|
|
3
|
+
Componha `Pagination`, `PaginationContent` e um `PaginationItem` para cada link. `page` fornece o
|
|
4
|
+
número e o nome acessível; `isActive` identifica a página atual. Os controles mantêm altura fixa e
|
|
5
|
+
a largura cresce quando o número precisa de mais espaço. `ActionList` usa esta mesma primitiva no
|
|
6
|
+
rodapé.
|
|
8
7
|
|
|
9
8
|
```tsx preview
|
|
10
9
|
<Pagination>
|
|
@@ -30,7 +29,7 @@ colam. As setas seguem quadradas e já falam pt-BR; `label` localiza, `iconOnly`
|
|
|
30
29
|
|
|
31
30
|
## Números longos
|
|
32
31
|
|
|
33
|
-
A largura mínima é quadrada
|
|
32
|
+
A largura mínima é quadrada e cresce conforme o conteúdo. Na escala densa de um rodapé, ajuste por
|
|
34
33
|
`className` (`h-7 min-w-7 text-xs` nos números; `size-7` mais `iconClassName="size-3.5"` nas
|
|
35
34
|
setas com `iconOnly`).
|
|
36
35
|
|
|
@@ -61,7 +60,8 @@ setas com `iconOnly`).
|
|
|
61
60
|
|
|
62
61
|
## Com elipse
|
|
63
62
|
|
|
64
|
-
PaginationEllipsis
|
|
63
|
+
`PaginationEllipsis` marca, de forma decorativa e com `aria-hidden`, uma sequência de páginas que
|
|
64
|
+
não cabe na barra.
|
|
65
65
|
|
|
66
66
|
```tsx preview
|
|
67
67
|
<Pagination>
|
|
@@ -123,14 +123,19 @@ render(
|
|
|
123
123
|
)
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
##
|
|
126
|
+
## Propriedades de PaginationLink
|
|
127
127
|
|
|
128
|
-
|
|
|
128
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
129
129
|
|---|---|---|---|
|
|
130
|
-
| `page
|
|
131
|
-
| `isActive
|
|
132
|
-
| `href
|
|
133
|
-
| `size
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
| `page` | `number` | | Número usado como conteúdo, quando `children` não é informado, e no nome acessível “Página N”. |
|
|
131
|
+
| `isActive` | `boolean` | `false` | Marca a página atual com `aria-current="page"` e tratamento `outline`. |
|
|
132
|
+
| `href` | `string` | | Renderiza um `<a>`. Sem `href`, o componente usa `<button>` e aceita `onClick` e `disabled`. |
|
|
133
|
+
| `size` | `'default' \| 'sm' \| 'lg' \| 'icon' \| 'icon-sm' \| 'icon-xs'` | `'default'` | Escala herdada de `Button`. A largura mínima cresce para acomodar números longos. |
|
|
134
|
+
|
|
135
|
+
## Propriedades de PaginationPrevious e PaginationNext
|
|
136
|
+
|
|
137
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
138
|
+
|---|---|---|---|
|
|
139
|
+
| `label` | `string` | `'Página anterior'` ou `'Próxima página'` | Nome acessível e texto visível da ação. |
|
|
140
|
+
| `iconOnly` | `boolean` | `false` | Exibe somente a seta; `label` continua disponível para leitura assistiva. |
|
|
141
|
+
| `iconClassName` | `string` | | Classes aplicadas ao ícone da seta. |
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Painel ancorado
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Popover` para apresentar conteúdo livre junto a um gatilho, sem abrir um modal.
|
|
4
|
+
`PopoverHeader` agrupa título e descrição; o restante do painel aceita formulários curtos ou
|
|
5
|
+
detalhes.
|
|
4
6
|
|
|
5
7
|
```tsx preview
|
|
6
8
|
<Popover>
|
|
@@ -23,7 +25,7 @@ PopoverHeader agrupa título e descrição; o corpo é livre (form curto, detalh
|
|
|
23
25
|
|
|
24
26
|
## Alinhamento
|
|
25
27
|
|
|
26
|
-
align posiciona o painel em relação ao gatilho
|
|
28
|
+
`align` posiciona o painel em relação ao gatilho e `sideOffset` define a distância entre eles.
|
|
27
29
|
|
|
28
30
|
```tsx preview
|
|
29
31
|
<Popover>
|
|
@@ -40,10 +42,22 @@ align posiciona o painel em relação ao gatilho; sideOffset afasta. O padrão (
|
|
|
40
42
|
</Popover>
|
|
41
43
|
```
|
|
42
44
|
|
|
43
|
-
##
|
|
45
|
+
## Propriedades de Popover
|
|
44
46
|
|
|
45
|
-
|
|
|
47
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
46
48
|
|---|---|---|---|
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
|
|
49
|
+
| `open` | `boolean` | | Estado no modo controlado. |
|
|
50
|
+
| `onOpenChange` | `(open: boolean) => void` | | Atualiza o estado para permitir abertura ou fechamento por código. |
|
|
51
|
+
|
|
52
|
+
## Propriedades de PopoverAnchor
|
|
53
|
+
|
|
54
|
+
`PopoverAnchor` posiciona o painel em relação a um elemento que não é o gatilho — útil quando o
|
|
55
|
+
clique acontece num item de linha, mas o painel deve alinhar-se ao contêiner. Aceita `asChild` para
|
|
56
|
+
não introduzir um nó extra; sem ele, o `PopoverTrigger` é a âncora.
|
|
57
|
+
|
|
58
|
+
## Propriedades de PopoverContent
|
|
59
|
+
|
|
60
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| `align` | `'start' \| 'center' \| 'end'` | `'center'` | Alinhamento do painel em relação ao gatilho. |
|
|
63
|
+
| `sideOffset` | `number` | `4` | Distância entre o gatilho e o painel. |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
##
|
|
1
|
+
## Progresso determinado
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `Progress` quando a tarefa informar uma porcentagem de conclusão. `value` aceita valores de 0 a
|
|
4
|
+
100 e anima o preenchimento a cada mudança. Sem valor, a barra permanece vazia.
|
|
4
5
|
|
|
5
6
|
```tsx preview col
|
|
6
7
|
<div className="w-full max-w-sm space-y-2">
|
|
@@ -14,7 +15,8 @@ value vai de 0 a 100 — o preenchimento anima a cada mudança. Sem value (ou nu
|
|
|
14
15
|
|
|
15
16
|
## Controlado
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
Controle `value` externamente quando o progresso acompanhar uma tarefa em andamento. No exemplo,
|
|
19
|
+
cada clique avança uma etapa da sincronização.
|
|
18
20
|
|
|
19
21
|
```tsx preview col
|
|
20
22
|
const [step, setStep] = useState(40)
|
|
@@ -38,32 +40,29 @@ render(
|
|
|
38
40
|
)
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
## Altura e
|
|
43
|
+
## Altura e contexto
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
`context` diz o que o andamento comunica — `success` para meta atingida, `warning` ou `danger`
|
|
46
|
+
para atenção — e tinge trilha e preenchimento com a família semântica. `className` compõe sobre o
|
|
47
|
+
padrão para ajustar a altura.
|
|
44
48
|
|
|
45
49
|
```tsx preview col
|
|
46
50
|
<div className="w-full max-w-sm space-y-4">
|
|
47
51
|
<div className="space-y-1.5">
|
|
48
52
|
<span className="text-sm">Cobertura de testes</span>
|
|
49
|
-
<Progress
|
|
50
|
-
value={92}
|
|
51
|
-
className="h-1.5 [&_[data-slot=progress-indicator]]:bg-emerald-500"
|
|
52
|
-
/>
|
|
53
|
+
<Progress value={92} context="success" className="h-1.5" />
|
|
53
54
|
</div>
|
|
54
55
|
<div className="space-y-1.5">
|
|
55
56
|
<span className="text-sm">Skills cobertas pelo revisor</span>
|
|
56
|
-
<Progress
|
|
57
|
-
value={45}
|
|
58
|
-
className="h-3 [&_[data-slot=progress-indicator]]:bg-amber-500"
|
|
59
|
-
/>
|
|
57
|
+
<Progress value={45} context="warning" className="h-3" />
|
|
60
58
|
</div>
|
|
61
59
|
</div>
|
|
62
60
|
```
|
|
63
61
|
|
|
64
|
-
##
|
|
62
|
+
## Propriedades de Progress
|
|
65
63
|
|
|
66
|
-
|
|
|
64
|
+
| Propriedade | Tipo | Padrão | Descrição |
|
|
67
65
|
|---|---|---|---|
|
|
68
66
|
| `value` | `number \| null` | | O progresso de 0 a 100. O preenchimento anima a cada mudança; null/ausente deixa a barra vazia. |
|
|
69
|
-
| `
|
|
67
|
+
| `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'primary'` | O que o andamento comunica; tinge trilha e preenchimento pela família `--context-*`. |
|
|
68
|
+
| `className` | `string` | | Compõe sobre o padrão — ajuste a altura (h-1.5/h-3). |
|
|
@@ -4,8 +4,8 @@ title: Filas
|
|
|
4
4
|
|
|
5
5
|
# Filas
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Use uma action em background quando a operação não puder manter a requisição aberta. O
|
|
8
|
+
`QueueAdapter` enfileira o trabalho e devolve um `JobHandle` para acompanhamento pelo cliente.
|
|
9
9
|
|
|
10
10
|
## O contrato
|
|
11
11
|
|
|
@@ -44,7 +44,7 @@ const queue = bullmqQueue({ queue: new Queue('opus', { connection: { host: 'loca
|
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
Redis-backed (durável, entre-processos, retries). Peers opcionais (`bullmq`, `ioredis`) só
|
|
47
|
-
|
|
47
|
+
para quem usa o driver.
|
|
48
48
|
|
|
49
49
|
## No runtime
|
|
50
50
|
|
|
@@ -82,7 +82,7 @@ Multiplicador exponencial diferente de 2 ou `maxMs` exige `backoffStrategy` cust
|
|
|
82
82
|
é rejeitado pelo driver em vez de ser silenciosamente ignorado. `timeout` permanece no
|
|
83
83
|
envelope para o worker aplicar, pois não é uma opção de execução do `Queue.add`.
|
|
84
84
|
|
|
85
|
-
## Limites
|
|
85
|
+
## Limites atuais
|
|
86
86
|
|
|
87
|
-
Driver hoje: `bullmq` (Redis). In-memory
|
|
87
|
+
Driver hoje: `bullmq` (Redis). In-memory para dev e outros backends (SQS, pg-boss…) entram
|
|
88
88
|
por reincidência.
|