@softize/opus 13.0.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/PROMOTED.md +46 -0
  3. package/README.md +28 -19
  4. package/bin/cli.mjs +87 -216
  5. package/bin/lib/cli-shared.mjs +131 -0
  6. package/bin/lib/db.mjs +16 -74
  7. package/bin/lib/gen-openapi.mjs +3 -3
  8. package/bin/lib/gen-runner.mjs +1 -1
  9. package/bin/lib/gen.mjs +14 -69
  10. package/bin/lib/mcp.mjs +3 -1
  11. package/bin/lib/seed.mjs +5 -62
  12. package/docs/ownership-vs-shadcn-lock.md +2 -3
  13. package/docs/protocol.md +7 -7
  14. package/docs/releasing.md +8 -2
  15. package/package.json +7 -3
  16. package/registry/templates/app/package.json +1 -1
  17. package/registry/templates/app/src/main.tsx +4 -4
  18. package/src/audit/drivers/console.ts +1 -0
  19. package/src/auth/drivers/better-auth.ts +1 -0
  20. package/src/auth/drivers/jwt.ts +1 -0
  21. package/src/cache/drivers/memory.ts +1 -0
  22. package/src/client/drivers/fetch.ts +2 -1
  23. package/src/core/actions.ts +6 -1
  24. package/src/core/audit.ts +9 -3
  25. package/src/core/contracts.ts +7 -0
  26. package/src/core/domain.ts +1 -1
  27. package/src/core/errors.ts +18 -15
  28. package/src/core/index.ts +4 -2
  29. package/src/core/package-version.ts +26 -0
  30. package/src/core/reactions.ts +1 -1
  31. package/src/core/runtime.ts +33 -23
  32. package/src/core/schedules.ts +1 -1
  33. package/src/core/types.ts +2 -2
  34. package/src/dsl/eval.ts +2 -2
  35. package/src/dsl/kysely.ts +2 -2
  36. package/src/dsl/loads.ts +1 -1
  37. package/src/dsl/parser.ts +5 -5
  38. package/src/events/drivers/mitt.ts +1 -0
  39. package/src/mcp/index.ts +2 -1
  40. package/src/observability/drivers/opentelemetry.ts +1 -0
  41. package/src/queue/drivers/bullmq.ts +3 -3
  42. package/src/scheduler/drivers/node-cron.ts +3 -2
  43. package/src/scheduler/every.ts +7 -7
  44. package/src/schema/openapi.ts +3 -3
  45. package/src/seed/index.ts +29 -0
  46. package/src/server/drivers/fastify.ts +5 -2
  47. package/src/server/drivers/node.ts +9 -6
  48. package/src/server/index.ts +3 -1
  49. package/src/storage/drivers/fs.ts +1 -0
  50. package/src/testing/index.ts +3 -3
  51. package/src/ui/components/patterns/confirm.tsx +2 -2
  52. package/src/ui/components/patterns/content-header.tsx +7 -1
  53. package/src/ui/components/patterns/data-state.tsx +1 -1
  54. package/src/ui/components/patterns/dock.tsx +20 -3
  55. package/src/ui/components/patterns/form.tsx +12 -8
  56. package/src/ui/components/patterns/list.tsx +4 -4
  57. package/src/ui/components/patterns/page.tsx +19 -1
  58. package/src/ui/components/patterns/shell-nav.tsx +10 -3
  59. package/src/ui/components/patterns/sidebar.tsx +17 -6
  60. package/src/ui/components/patterns/trigger.tsx +14 -16
  61. package/src/ui/components/patterns/view.tsx +26 -17
  62. package/src/ui/components/primitives/alert.tsx +11 -5
  63. package/src/ui/components/primitives/ask.tsx +3 -3
  64. package/src/ui/components/primitives/badge.tsx +11 -6
  65. package/src/ui/components/primitives/breadcrumb.tsx +2 -2
  66. package/src/ui/components/primitives/button.tsx +16 -3
  67. package/src/ui/components/primitives/calendar.tsx +28 -2
  68. package/src/ui/components/primitives/carousel.tsx +3 -3
  69. package/src/ui/components/primitives/chat.tsx +1 -1
  70. package/src/ui/components/primitives/checkbox.tsx +1 -1
  71. package/src/ui/components/primitives/command.tsx +2 -2
  72. package/src/ui/components/primitives/control.ts +12 -0
  73. package/src/ui/components/primitives/copyable.tsx +1 -1
  74. package/src/ui/components/primitives/dialog.tsx +12 -7
  75. package/src/ui/components/primitives/dot.tsx +5 -0
  76. package/src/ui/components/primitives/drawer.tsx +10 -3
  77. package/src/ui/components/primitives/field.tsx +3 -3
  78. package/src/ui/components/primitives/icon-picker.tsx +3 -1
  79. package/src/ui/components/primitives/input-group.tsx +1 -1
  80. package/src/ui/components/primitives/input-otp.tsx +1 -1
  81. package/src/ui/components/primitives/input.tsx +2 -2
  82. package/src/ui/components/primitives/progress.tsx +32 -3
  83. package/src/ui/components/primitives/radio-group.tsx +1 -1
  84. package/src/ui/components/primitives/resizable.tsx +3 -1
  85. package/src/ui/components/primitives/select.tsx +5 -5
  86. package/src/ui/components/primitives/slider.tsx +5 -1
  87. package/src/ui/components/primitives/sonner.tsx +3 -0
  88. package/src/ui/components/primitives/switch.tsx +1 -0
  89. package/src/ui/components/primitives/tabs.tsx +1 -0
  90. package/src/ui/components/primitives/textarea.tsx +1 -1
  91. package/src/ui/components/primitives/toggle.tsx +1 -1
  92. package/src/ui/components/primitives/tooltip.tsx +1 -0
  93. package/src/ui/docs/changelog.tsx +1 -1
  94. package/src/ui/docs/content/action-form.md +10 -3
  95. package/src/ui/docs/content/action-list.md +10 -1
  96. package/src/ui/docs/content/action-trigger.md +8 -1
  97. package/src/ui/docs/content/action-view.md +9 -2
  98. package/src/ui/docs/content/ask.md +11 -0
  99. package/src/ui/docs/content/calendar.md +13 -0
  100. package/src/ui/docs/content/card.md +26 -0
  101. package/src/ui/docs/content/chat.md +20 -0
  102. package/src/ui/docs/content/cli.md +71 -19
  103. package/src/ui/docs/content/composer.md +15 -0
  104. package/src/ui/docs/content/content.md +15 -0
  105. package/src/ui/docs/content/copyable.md +8 -0
  106. package/src/ui/docs/content/detail.md +19 -1
  107. package/src/ui/docs/content/dictionary-value.md +9 -2
  108. package/src/ui/docs/content/dock.md +8 -0
  109. package/src/ui/docs/content/dot.md +8 -0
  110. package/src/ui/docs/content/empty.md +2 -2
  111. package/src/ui/docs/content/getting-started.md +2 -2
  112. package/src/ui/docs/content/icon-picker.md +11 -0
  113. package/src/ui/docs/content/label.md +7 -0
  114. package/src/ui/docs/content/menu.md +6 -0
  115. package/src/ui/docs/content/metric-card.md +13 -0
  116. package/src/ui/docs/content/page.md +8 -0
  117. package/src/ui/docs/content/popover.md +6 -0
  118. package/src/ui/docs/content/progress.md +8 -11
  119. package/src/ui/docs/content/select.md +5 -5
  120. package/src/ui/docs/content/semantic-context.md +2 -2
  121. package/src/ui/docs/content/sidebar.md +14 -8
  122. package/src/ui/docs/content/skeleton.md +6 -0
  123. package/src/ui/docs/content/split.md +21 -0
  124. package/src/ui/docs/content/textarea.md +7 -0
  125. package/src/ui/docs/content/tokens.md +4 -4
  126. package/src/ui/docs/content/truncate.md +8 -0
  127. package/src/ui/docs/content/ui.md +14 -0
  128. package/src/ui/docs/doc-client.tsx +5 -5
  129. package/src/ui/docs/doc.tsx +26 -14
  130. package/src/ui/docs/registry.tsx +5 -5
  131. package/src/ui/docs/standalone.tsx +2 -2
  132. package/src/ui/drivers/react.tsx +17 -12
  133. package/src/ui/lib/action-errors.ts +45 -0
  134. package/src/ui/lib/zod-pt-br.ts +31 -4
  135. package/src/ui/meta.ts +3 -3
  136. package/src/ui/react.tsx +2 -0
  137. package/src/ui/theme.css +10 -8
  138. package/src/vite/design.ts +6 -18
@@ -64,6 +64,13 @@ negócio `conflict`, `validation` e `not_found`:
64
64
  Erros inesperados mantêm a mensagem genérica do contrato, evitando expor detalhes de banco ou
65
65
  infraestrutura. O servidor registra o detalhe técnico para diagnóstico.
66
66
 
67
+ ## useTriggerAction
68
+
69
+ `ActionTrigger` é uma composição sobre `useTriggerAction(action, { onSuccess, onError })`, que
70
+ devolve `trigger(input)`, `isLoading`, `error` e `reset`, e invalida o cache declarado em
71
+ `action.invalidates`. Use o hook direto quando a execução parte de um gesto que não é um botão —
72
+ um atalho, um arrastar, um item de menu.
73
+
67
74
  ## Propriedades de ActionTrigger
68
75
 
69
76
  | Propriedade | Tipo | Padrão | Descrição |
@@ -71,7 +78,7 @@ infraestrutura. O servidor registra o detalhe técnico para diagnóstico.
71
78
  | `action` | `SimpleContract<TInput, TData>` | | A SimpleAction do Opus — label, messages e confirm vêm do contrato. |
72
79
  | `input` | `TInput` | | O que a action recebe — geralmente { id }. |
73
80
  | `label` | `string` | `action.label` | Sobrepõe o texto do botão. |
74
- | `variant / size` | `variants do Button` | `'default' (ou 'destructive' se o confirm declarar) / 'default'` | Visual do botão o destructive do ConfirmSpec escolhe sozinho. |
81
+ | `context / variant / size` | `do Button` | gatilho: `primary`/`solid`; `danger` quando a action é `destructive`; no modo ícone, `ghost` e `neutral` (ou `danger` se destrutiva) / `default` | Visual do gatilho; `context` explícito vence. O botão de confirmar é sempre `solid`: `danger` quando a action é `destructive`, senão a prop `context` do gatilho (ou `primary`). |
75
82
  | `confirm` | `{ title, description?, actionLabel?, cancelLabel? }` | | Confirmação via prop — sobrepõe o ConfirmSpec do contrato. |
76
83
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
77
84
  | `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza para o item. |
@@ -35,6 +35,12 @@ render(
35
35
  )
36
36
  ```
37
37
 
38
+ ## useViewAction
39
+
40
+ `ActionView` é uma composição sobre `useViewAction(action, input)`, que devolve `data`, `error`,
41
+ `isLoading` e `refetch` sob o `QueryClientProvider`. Use o hook direto quando o recurso alimenta
42
+ mais de uma região da tela ou quando o carregamento precisa ser orquestrado por outro componente.
43
+
38
44
  ## Propriedades de ActionView
39
45
 
40
46
  | Propriedade | Tipo | Padrão | Descrição |
@@ -43,5 +49,6 @@ render(
43
49
  | `input` | `TInput` | | Geralmente { id } — mudou, recarrega. |
44
50
  | `children` | `(data: TData, refetch) => ReactNode` | | Conteúdo apresentado quando os dados estão disponíveis. `refetch` permite recarregar por código. |
45
51
  | `render` | `(data: TData, refetch) => ReactNode` | | Alias de compatibilidade de `children`; `children` tem precedência. |
46
- | `loading / empty` | `ReactNode` | `3 skeletons / nada` | Sobrescreve os estados padrão quando o contexto pedir. |
47
- | `error` | `(err, retry) => ReactNode` | `mensagem + Tentar de novo` | Sobrescreve o estado de erro padrão. |
52
+ | `loading` | `ReactNode \| boolean` | `3 skeletons` | Sobrescreve o carregamento: um próprio, `true` para o padrão ou `false` para não renderizar nada enquanto carrega. |
53
+ | `empty` | `ReactNode` | `nada` | Sobrescreve o estado vazio (200 sem dado). |
54
+ | `error` | `(err, retry) => ReactNode` | `"Não foi possível carregar" + Tentar de novo` | Sobrescreve o estado de erro padrão. Sem ele, a frase do servidor só aparece quando é legível pela pessoa (`conflict`, `validation`, `not_found`, `authorization`, `authentication`); código técnico nunca vira título, e o botão de tentar de novo some quando o problema é de permissão ou sessão. |
@@ -29,3 +29,14 @@ render(
29
29
  ```
30
30
 
31
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).
32
+
33
+ ## Propriedades de Ask
34
+
35
+ | Propriedade | Tipo | Padrão | Descrição |
36
+ |---|---|---|---|
37
+ | `questions` | `AskQuestion[]` | | Uma a quatro perguntas do contrato de elicitação; fora dessa faixa o envio é bloqueado. |
38
+ | `answers` | `AskAnswer[]` | | Respostas controladas, na mesma ordem de `questions`; entradas ausentes contam como vazias. |
39
+ | `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. |
@@ -1,3 +1,7 @@
1
+ O calendário fala pt-BR por padrão: nomes de mês e de dia vêm do `locale` (`ptBR` de
2
+ `date-fns/locale`) e os controles de navegação têm rótulos acessíveis em português. Passe outro
3
+ `locale` para trocar o idioma; `labels` sobrescreve rótulos individualmente.
4
+
1
5
  ## Dia único
2
6
 
3
7
  mode=single guarda uma Date — controle por selected/onSelect. defaultMonth abre o calendário no mês certo sem mexer na seleção.
@@ -60,3 +64,12 @@ captionLayout=dropdown troca o título do mês por seletores de mês e ano — b
60
64
  | `disabled` | `Matcher` | | Datas não selecionáveis — uma Date, um array, um { from, to } ou um predicado (date) => boolean. |
61
65
  | `buttonVariant` | `Button['variant']` | `'ghost'` | A variante dos botões de navegação (anterior/próximo). |
62
66
  | `showOutsideDays` | `boolean` | `true` | Mostra os dias do mês vizinho que completam a primeira e a última semana. |
67
+ | `locale` | `Locale` | `ptBR` | Idioma dos nomes de mês e de dia (um locale do date-fns). |
68
+ | `labels` | `Partial<Labels>` | rótulos em pt-BR | Rótulos acessíveis da navegação e dos seletores; mescla sobre o padrão. |
69
+
70
+ ## CalendarDayButton
71
+
72
+ Cada dia é um `CalendarDayButton` — um `Button` ghost quadrado que recebe os modificadores do
73
+ dia (`selected`, `range-start`, `today`…). Use `components={{ DayButton: … }}` para
74
+ decorar o dia (um marcador de evento, por exemplo) partindo dele em vez de reimplementar o
75
+ foco e a seleção.
@@ -47,3 +47,29 @@ Para painel com estrutura: cada slot é dono do próprio padding (como o Dialog)
47
47
  </CardFooter>
48
48
  </Card>
49
49
  ```
50
+
51
+ ## Ação no cabeçalho
52
+
53
+ `CardAction` é o slot de ação do header (um botão ou menu). Quem compõe posiciona — em geral um
54
+ `CardHeader` em `flex-row` com o título de um lado e a ação do outro.
55
+
56
+ ```tsx preview col
57
+ <Card>
58
+ <CardHeader className="flex-row items-start justify-between">
59
+ <div>
60
+ <CardTitle>Empresa X</CardTitle>
61
+ <CardDescription>3 agentes vinculados.</CardDescription>
62
+ </div>
63
+ <CardAction>
64
+ <Button size="sm" variant="outline">Editar</Button>
65
+ </CardAction>
66
+ </CardHeader>
67
+ </Card>
68
+ ```
69
+
70
+ ## Propriedades de Card
71
+
72
+ | Propriedade | Tipo | Padrão | Descrição |
73
+ |---|---|---|---|
74
+ | `asChild` | `boolean` | `false` | Renderiza o filho com a superfície do Card (ex.: um `<button>` clicável inteiro). |
75
+ | `className` | `string` | | Compõe sobre a superfície; os slots (`CardHeader`, `CardBody`, `CardContent`, `CardFooter`, `CardAction`) são donos do próprio padding. |
@@ -91,3 +91,23 @@ continua valendo — é o caso degenerado do protocolo.
91
91
  Reidratação: passe `initialMessages` com o histórico persistido e troque a `key` do
92
92
  componente ao trocar de conversa. O rótulo do indicador é customizável por
93
93
  `humanizeTool={(name) => '…'}`.
94
+
95
+ ## Propriedades de Chat
96
+
97
+ | Propriedade | Tipo | Padrão | Descrição |
98
+ |---|---|---|---|
99
+ | `send` | `(messages: ChatMessage[]) => Promise<string> \| AsyncIterable<ChatEvent>` | | Modo autogerenciado: envia o histórico e devolve a resposta inteira ou um stream de eventos. Ignorado no modo controlado. |
100
+ | `messages` | `ChatTranscriptItem[]` | | Modo controlado: o transcript vem do app; com ele, `onSend`, `busy` e `activity` assumem. |
101
+ | `onSend` | `(text: string) => void \| boolean \| Promise<void \| boolean>` | | Modo controlado: recebe o texto enviado; retornar `false` devolve o texto ao composer. |
102
+ | `busy` | `boolean` | | Modo controlado: trava o composer enquanto o turno corre. |
103
+ | `activity` | `string \| null` | `undefined` | Indicador vivo: `null` mostra “Pensando…”, string mostra o rótulo; `undefined` esconde. |
104
+ | `notice` | `ReactNode` | | Aviso do app acima do composer (credencial, agente desatualizado…). |
105
+ | `composerActions` | `ReactNode` | | Seletores discretos na barra do composer (agente, app, escopo). |
106
+ | `composerClassName` | `string` | | Ajusta o contêiner externo do composer sem alcançar o DOM interno. |
107
+ | `greeting` | `string` | | Texto do estado vazio; some quando a conversa começa. |
108
+ | `empty` | `ReactNode` | | Estado vazio composto pelo app; vence `greeting` quando os dois existem. |
109
+ | `initialMessages` | `ChatMessage[]` | | Histórico inicial do modo autogerenciado; troque a `key` ao trocar de conversa. |
110
+ | `kickoff` | `() => Promise<string> \| AsyncIterable<ChatEvent>` | | Conversa que começa pelo assistente, uma vez, quando o transcript nasce vazio. |
111
+ | `renderArtifact` | `(artifact: ChatArtifact) => ReactNode` | link com o título | Render do evento `artifact`. |
112
+ | `humanizeTool` | `(name: string, detail?: string) => string` | pt-BR embutido | Rótulo humano do tool em uso no indicador vivo. |
113
+ | `placeholder` | `string` | `'Escreva uma mensagem…'` | Placeholder do composer. |
@@ -5,18 +5,39 @@ title: CLI opus
5
5
  # CLI opus
6
6
 
7
7
  O Opus traz um CLI que cobre o ciclo: faz o bootstrap, gera artefatos a partir das declarações,
8
- valida as convenções e expõe o estado vivo para os agentes via MCP.
8
+ valida as convenções e expõe o estado vivo para os agentes via MCP. `opus --help` lista os
9
+ comandos e `opus <comando> --help` detalha cada um.
9
10
 
10
11
  ## Gates
11
12
 
12
- > Use o check no CI e antes de entregar uma mudança. Ele informa quais contratos precisam de
13
- > ajuste e encerra com sucesso quando não encontra violações.
13
+ > Use os gates no CI e antes de entregar uma mudança. Cada um informa o que precisa de ajuste e
14
+ > encerra com sucesso (exit 0) quando não encontra violação.
14
15
 
15
16
  ```bash
16
- opus check src # valida as convenções das actions (exit ≠ 0 se violar)
17
- opus db check # drift entidade banco (read-only)
18
- opus db migrate # aplica o schema idempotente + drift-check na sequência
19
- opus seed check # valida bindings, dependências, ciclos e comandos paralelos
17
+ opus check src # convenções das actions e da UI (exit ≠ 0 se violar)
18
+ opus copy --check # inventário de copy ausente ou desatualizado
19
+ opus db check # drift entidade banco (read-only)
20
+ opus seed check # bindings, dependências, ciclos e scripts paralelos de seed
21
+ ```
22
+
23
+ `opus check` lê o source sem executar nada e aplica nove regras: cinco sobre as actions
24
+ (`action-name`, `kind`, `field-order`, `export`, `requires-sem-authorize`) e quatro sobre a UI
25
+ (`ui-structure`, `ui-semantic-api`, `removed-ui-token`, `unpaired-ui-surface`). Um projeto
26
+ marcado com `opus.json` e ainda sem actions passa vacuamente; sem o marcador, zero actions
27
+ falha, porque um gate vazio não é aprovação. `opus check --help` descreve cada regra.
28
+
29
+ ### Hook de pré-push
30
+
31
+ O hook Git que `opus setup` instala roda, antes de cada push: `opus copy --check`; depois
32
+ `opus pre-push materialization` na raiz, para confirmar que os artefatos Opus materializados
33
+ (skills, hooks, instruções) correspondem à versão adotada; e `opus check` em cada app com
34
+ `opus.json`. Qualquer um com exit ≠ 0 bloqueia o push. Os mesmos comandos podem ser executados à
35
+ mão para antecipar o resultado:
36
+
37
+ ```bash
38
+ opus pre-push # o mesmo opus check no diretório atual
39
+ opus pre-push apps/portal # o check de um app específico
40
+ opus pre-push materialization # só a freshness dos artefatos materializados
20
41
  ```
21
42
 
22
43
  ## Seeds de desenvolvimento e teste
@@ -31,9 +52,11 @@ opus seed apply customers.scenarios --profile smoke --scope local
31
52
  opus seed verify customers.scenarios --profile smoke --scope local
32
53
  ```
33
54
 
34
- `apply` converge quando repetido; não reset ou truncate no contrato. A skill
35
- `$create-opus-seed` estrutura um novo dataset, e `$apply-opus-seed` opera um seed registrado pela
36
- mesma CLI.
55
+ `--profile` escolhe o perfil (default: o `defaultProfile` do seed) e `--scope` declara o escopo
56
+ dos dados (a variável `OPUS_SEED_SCOPE` é a alternativa). `--json` devolve o resultado
57
+ estruturado, inclusive em caso de erro. `apply` converge quando repetido; não há reset ou
58
+ truncate no contrato. A skill `$create-opus-seed` estrutura um novo dataset, e
59
+ `$apply-opus-seed` opera um seed registrado pela mesma CLI.
37
60
 
38
61
  ## Geração e introspecção
39
62
 
@@ -41,23 +64,52 @@ mesma CLI.
41
64
  > é um lockfile versionado. A diferença do manifest torna a revisão objetiva.
42
65
 
43
66
  ```bash
44
- opus gen # manifest / openapi / docs / stubs a partir do opus.config.ts
45
- opus introspect # modelo da estrutura (actions/reactions/schedules + wiring)
67
+ opus gen # manifest / openapi / docs / stubs a partir do opus.config.ts
68
+ opus gen --config ./apps/api/opus.config.ts # config fora do diretório atual
69
+ opus gen --output ./custom/gen # pasta de saída (default: a do config, ou ./.gen)
70
+ opus copy # inventário semântico de copy dos contratos
71
+ opus introspect # modelo da estrutura (actions/reactions/schedules + wiring)
46
72
  opus introspect --json
47
73
  ```
48
74
 
49
- ## Bootstrap e templates
75
+ `opus copy` projeta os textos de `defineAction`/`defineContract` no inventário que a política de
76
+ copy da `@softize/base` valida. O caminho vem de `base.json` (`copy.inventory`; default
77
+ `.base/copy-inventory.json` na raiz Git), a escrita é atômica e o arquivo gerado deve ser
78
+ versionado. `--check` compara sem escrever e falha quando o inventário está ausente,
79
+ desatualizado ou contém copy dinâmica não declarada.
80
+
81
+ `--config` vale para `gen`, `db` e `seed` e sempre aponta para um caminho dentro do projeto. O
82
+ `gen` aceita `--force` por compatibilidade, mas a flag não altera nada: a saída mora em um
83
+ diretório dedicado e é sempre sobrescrita.
84
+
85
+ ## Banco
86
+
87
+ > Os comandos `db` carregam o `opus.config.ts` e abrem conexão; por isso rodam num runner isolado
88
+ > via `tsx`. O padrão é um schema idempotente: um script SQL evolutivo re-rodável, não migrations
89
+ > versionadas.
90
+
91
+ ```bash
92
+ opus db check # compara o schema do banco com as entidades (read-only)
93
+ opus db migrate # aplica o schema idempotente e roda o drift-check na sequência
94
+ opus db scaffold # rascunho kysely a partir do diff, como referência para escrever o SQL
95
+ ```
96
+
97
+ `db scaffold` gera um rascunho para revisão à mão; a verdade continua sendo o script. `db migrate
98
+ down` está aposentado e encerra com erro: sem histórico de migrations não há o que reverter —
99
+ rollback é editar o schema e rodar `opus db migrate` de novo. O `opus.config.ts` precisa expor a
100
+ factory lazy `database`, as entidades (ou domínios) e, opcionalmente, `schema` e `migrations`;
101
+ `opus db --help` detalha.
102
+
103
+ ## Bootstrap
50
104
 
51
105
  > `create` scaffolda um app novo com os pré-requisitos plugados; `setup` é per-app
52
- > (idempotente, nunca sobrescreve o seu); `add` copia um template do catálogo empacotado.
106
+ > (idempotente, nunca sobrescreve o seu).
53
107
 
54
108
  ```bash
55
- opus create meu-app # app canônico do zero: protocolo + UI + preview + automação
109
+ opus create meu-app # app canônico do zero: protocolo + UI + preview + automação
56
110
  opus create meu-cliente --monorepo # a RAIZ de um workspace (apps/* + packages/*)
57
- opus create apps/portal # dentro de um workspace: só o app (modo detectado)
58
- opus setup # grava opus.json e materializa a camada específica do SDK
59
- opus list # lista os templates disponíveis
60
- opus add action-form # copia um template do catálogo para o projeto
111
+ opus create apps/portal # dentro de um workspace: só o app (modo detectado)
112
+ opus setup # grava opus.json e materializa a camada específica do SDK
61
113
  ```
62
114
 
63
115
  O esqueleto do `create` versiona com o Opus (sai do mesmo pacote que o SDK que ele
@@ -48,3 +48,18 @@ render(
48
48
  />,
49
49
  )
50
50
  ```
51
+
52
+ ## Propriedades de Composer
53
+
54
+ | Propriedade | Tipo | Padrão | Descrição |
55
+ |---|---|---|---|
56
+ | `value` | `string` | | Texto controlado; o dono do estado é quem compõe. |
57
+ | `onChange` | `(value: string) => void` | | Recebe cada alteração do texto. |
58
+ | `onSubmit` | `() => void` | | Enter (sem Shift) ou o botão enviar; só dispara quando dá para enviar. |
59
+ | `onHistoryPrevious` / `onHistoryNext` | `() => boolean` | | Navegação por ↑/↓ no histórico do dono; retorne `true` quando a tecla foi consumida. |
60
+ | `busy` | `boolean` | `false` | Trava o composer enquanto o turno corre; o enviar vira spinner. |
61
+ | `submitDisabled` | `boolean` | `false` | Gate extra de envio além de vazio e `busy` (ex.: falta escolher o app). |
62
+ | `placeholder` | `string` | `'Escreva uma mensagem…'` | Texto de orientação do campo. |
63
+ | `rows` | `number` | `1` | Linhas iniciais do textarea; ele cresce com o conteúdo. |
64
+ | `autoFocus` | `boolean` | | Foca o campo ao montar. |
65
+ | `actions` | `ReactNode` | | Controles discretos à esquerda do enviar; presente, o composer vira duas linhas. |
@@ -42,3 +42,18 @@ render(
42
42
 
43
43
  As duas formas geram os mesmos elementos, estilos e `data-slot`. `opus check` reprova slots fora
44
44
  do pai correto, filhos estruturais indiretos e a mistura de shorthand com composição explícita.
45
+
46
+ ## Propriedades de Content
47
+
48
+ | Propriedade | Tipo | Padrão | Descrição |
49
+ |---|---|---|---|
50
+ | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
51
+ | `meta` | `ReactNode` | | Forma curta: complemento ao lado do título, como uma contagem. |
52
+ | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
53
+ | `actions` | `ReactNode` | | Forma curta: ações alinhadas à direita do header. |
54
+ | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | herdado | Nível semântico do heading, independente do destaque visual. |
55
+ | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual; `page` permanece só por compatibilidade da série 12. |
56
+
57
+ Na composição explícita, `ContentHeader` recebe `ContentTitle`, `ContentMeta`, `ContentDescription` e
58
+ `ContentActions`, e `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level`
59
+ próprios — a hierarquia é declarada em `Content`.
@@ -29,3 +29,11 @@ Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar n
29
29
  copiar (3s)
30
30
  </Copyable>
31
31
  ```
32
+
33
+ ## Propriedades de Copyable
34
+
35
+ | Propriedade | Tipo | Padrão | Descrição |
36
+ |---|---|---|---|
37
+ | `value` | `string` | | O texto que vai para a área de transferência no clique. |
38
+ | `feedbackMs` | `number` | `1500` | Duração do estado “copiado”, em milissegundos. |
39
+ | `children` | `ReactNode` | | Rótulo ao lado do ícone; sem ele, o controle é só o ícone com nome acessível. |
@@ -9,7 +9,7 @@ validação, use `Field`.
9
9
  label="Estágio"
10
10
  value={
11
11
  <DictionaryValue
12
- dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospect' }, customer: { label: 'Cliente', context: 'success' } }, presentation: 'stage' }}
12
+ dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospecto' }, customer: { label: 'Cliente', context: 'success' } }, presentation: 'stage' }}
13
13
  value="prospect"
14
14
  />
15
15
  }
@@ -56,3 +56,21 @@ mais espaço para os rótulos, ajuste a variável no grupo, por exemplo com
56
56
  />
57
57
  </DetailGroup>
58
58
  ```
59
+
60
+ ## Propriedades de DetailGroup
61
+
62
+ | Propriedade | Tipo | Padrão | Descrição |
63
+ |---|---|---|---|
64
+ | `variant` | `'plain' \| 'framed'` | `'plain'` | `framed` aplica a superfície e a moldura canônicas ao conjunto. |
65
+ | `dividers` | `boolean` | `false` | Hairlines somente entre os campos, sem exigir moldura externa. |
66
+ | `columns` | `1 \| 2 \| 3 \| 4 \| 'auto'` | `1` | Colunas responsivas ou distribuição automática por largura mínima. |
67
+ | `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Chave sobre o valor ou ao lado dele em cada campo. |
68
+
69
+ ## Propriedades de DetailField
70
+
71
+ | Propriedade | Tipo | Padrão | Descrição |
72
+ |---|---|---|---|
73
+ | `label` | `ReactNode` | | A chave do par. |
74
+ | `value` | `ReactNode` | | O valor; `null`, `undefined` e string vazia renderizam a ausência, `0` e `false` seguem como valores. |
75
+ | `icon` | `ReactNode` | | Ícone decorativo antes do par chave/valor. |
76
+ | `empty` | `ReactNode` | `“Não informado”` | O que a ausência significa neste campo: um rótulo ou um nó próprio. |
@@ -8,7 +8,7 @@ sem perder o rótulo visível.
8
8
  value="pj"
9
9
  />
10
10
  <DictionaryValue
11
- dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospect' }, customer: { label: 'Cliente', context: 'success' } }, presentation: 'stage' }}
11
+ dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospecto' }, customer: { label: 'Cliente', context: 'success' } }, presentation: 'stage' }}
12
12
  value="customer"
13
13
  />
14
14
  <DictionaryValue
@@ -37,7 +37,7 @@ export const customerKindDict = t.dict(
37
37
 
38
38
  export const customerStageDict = t.dict(
39
39
  {
40
- prospect: { label: 'Prospect', description: 'Relacionamento ainda em prospecção.' },
40
+ prospect: { label: 'Prospecto', description: 'Relacionamento ainda em prospecção.' },
41
41
  customer: { label: 'Cliente', context: 'success' },
42
42
  },
43
43
  { doc: 'Estágio comercial atual da parte.', presentation: 'stage' },
@@ -106,6 +106,13 @@ dicionário registrado no provider: `{ key: 'source', label: 'Fonte', dictionary
106
106
  Dimensões independentes (tipo e estágio, por exemplo) ficam em colunas distintas; não empilhar
107
107
  uma sob a outra como texto secundário.
108
108
 
109
+ ## useDicts
110
+
111
+ `useDicts()` devolve os dicionários registrados em `OpusProvider` (`dicts`), chaveados pela `ref`.
112
+ É o que `ActionList`, `ActionForm` e `DictionaryValue` consultam para resolver
113
+ `options: { kind: 'dictionary', ref }` e colunas nomeadas por `dictionary`; fora do provider, o
114
+ resultado é vazio e a resolução cai na meta do `t.dict` que viaja no schema.
115
+
109
116
  ## Propriedades de DictionaryValue
110
117
 
111
118
  | Propriedade | Tipo | Padrão | Descrição |
@@ -67,3 +67,11 @@ já monta.
67
67
  | --- | --- | --- | --- |
68
68
  | `position` | `'bottom' \| 'bottom-left' \| 'bottom-right'` | `'bottom'` | Aresta do contêiner onde a barra se ancora. |
69
69
  | `label` | `string` | | Nome acessível da barra. |
70
+
71
+ ## Propriedades de SurfaceStatus
72
+
73
+ | Propriedade | Tipo | Padrão | Descrição |
74
+ | --- | --- | --- | --- |
75
+ | `position` | `'top-right' \| 'top-left'` | `'top-right'` | Canto da superfície onde o estado se ancora. |
76
+ | `context` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | O que o estado comunica; tinge borda e texto pela família semântica. |
77
+ | `actions` | `ReactNode` | | Ações do recurso aberto, fora da região viva. |
@@ -15,3 +15,11 @@ acessível. Sem `label`, o ponto é decorativo.
15
15
  ```
16
16
 
17
17
  Use `Badge` quando o estado precisar permanecer legível sem depender do contexto ao redor.
18
+
19
+ ## Propriedades de Dot
20
+
21
+ | Propriedade | Tipo | Padrão | Descrição |
22
+ |---|---|---|---|
23
+ | `context` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | O significado da cor. |
24
+ | `variant` | `'solid' \| 'outline'` | `'solid'` | Ponto preenchido ou só contornado. |
25
+ | `label` | `string` | | Nome acessível quando a cor comunica estado; sem ele o ponto é decorativo (`aria-hidden`). |
@@ -53,8 +53,8 @@ título e descrição agrupados no centro.
53
53
 
54
54
  ## Mídia sem moldura
55
55
 
56
- `EmptyMedia variant="default"` não desenha fundo. Use essa variação para ilustrações ou mídias que
57
- tenham presença visual própria.
56
+ `EmptyMedia` sem `variant` (o padrão, `default`) não desenha fundo. Use essa forma para ilustrações
57
+ ou mídias que tenham presença visual própria.
58
58
 
59
59
  ```tsx preview col
60
60
  <Empty>
@@ -1,8 +1,8 @@
1
1
  ---
2
- title: Getting started
2
+ title: Primeiros passos
3
3
  ---
4
4
 
5
- # Getting started
5
+ # Primeiros passos
6
6
 
7
7
  O Opus ajuda cliente e servidor a preservar as mesmas regras à medida que uma aplicação
8
8
  evolui. Entidades e contratos de action ficam em uma camada compartilhada; o runtime, a UI e
@@ -53,3 +53,14 @@ render(
53
53
  />,
54
54
  )
55
55
  ```
56
+
57
+ ## Propriedades de IconPicker
58
+
59
+ | Propriedade | Tipo | Padrão | Descrição |
60
+ |---|---|---|---|
61
+ | `value` | `string` | | Nome do ícone selecionado (chave da paleta); `''` é nenhum. |
62
+ | `onChange` | `(name: string) => void` | | Recebe o nome escolhido. |
63
+ | `icons` | `Record<string, LucideIcon>` | `iconPickerIcons` | Paleta nome → componente. |
64
+ | `placeholder` | `string` | `'Selecione um ícone…'` | Texto do gatilho sem seleção. |
65
+ | `disabled` | `boolean` | | Desabilita o gatilho. |
66
+ | `id`, `aria-invalid`, `aria-describedby` | | | Integração com `Field`/`ActionForm`: o gatilho recebe a identidade e o estado de erro do campo. |
@@ -32,3 +32,10 @@ uma classe adicional.
32
32
  <Label htmlFor="archive-workspace">Arquivar o workspace</Label>
33
33
  </div>
34
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]`. |
@@ -111,6 +111,12 @@ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
111
111
  | `context` | `'neutral' \| 'danger'` | `'neutral'` | `danger` sinaliza uma ação com consequência perigosa. |
112
112
  | `inset` | `boolean` | | Alinha um item sem ícone com os itens que possuem ícone. |
113
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
+
114
120
  ## Propriedades de MenuContent
115
121
 
116
122
  | Propriedade | Tipo | Padrão | Descrição |
@@ -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. |
@@ -41,6 +41,14 @@ render(
41
41
  )
42
42
  ```
43
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
+
44
52
  ## Composição explícita
45
53
 
46
54
  Use os slots quando a página precisar compor o header diretamente. Não misture propriedades da
@@ -49,6 +49,12 @@ detalhes.
49
49
  | `open` | `boolean` | | Estado no modo controlado. |
50
50
  | `onOpenChange` | `(open: boolean) => void` | | Atualiza o estado para permitir abertura ou fechamento por código. |
51
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
+
52
58
  ## Propriedades de PopoverContent
53
59
 
54
60
  | Propriedade | Tipo | Padrão | Descrição |
@@ -40,25 +40,21 @@ render(
40
40
  )
41
41
  ```
42
42
 
43
- ## Altura e cor
43
+ ## Altura e contexto
44
44
 
45
- className compõe sobre o padrão: ajuste a altura no Progress e tinja o preenchimento mirando o slot do indicador (&_[data-slot=progress-indicator]) para sinalizar bom/atenção.
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.
46
48
 
47
49
  ```tsx preview col
48
50
  <div className="w-full max-w-sm space-y-4">
49
51
  <div className="space-y-1.5">
50
52
  <span className="text-sm">Cobertura de testes</span>
51
- <Progress
52
- value={92}
53
- className="h-1.5 [&_[data-slot=progress-indicator]]:bg-emerald-500"
54
- />
53
+ <Progress value={92} context="success" className="h-1.5" />
55
54
  </div>
56
55
  <div className="space-y-1.5">
57
56
  <span className="text-sm">Skills cobertas pelo revisor</span>
58
- <Progress
59
- value={45}
60
- className="h-3 [&_[data-slot=progress-indicator]]:bg-amber-500"
61
- />
57
+ <Progress value={45} context="warning" className="h-3" />
62
58
  </div>
63
59
  </div>
64
60
  ```
@@ -68,4 +64,5 @@ className compõe sobre o padrão: ajuste a altura no Progress e tinja o preench
68
64
  | Propriedade | Tipo | Padrão | Descrição |
69
65
  |---|---|---|---|
70
66
  | `value` | `number \| null` | | O progresso de 0 a 100. O preenchimento anima a cada mudança; null/ausente deixa a barra vazia. |
71
- | `className` | `string` | | Compõe sobre o padrão ajuste a altura (h-1.5/h-3) ou tinja o indicador via [&_[data-slot=progress-indicator]]:bg-*. |
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). |
@@ -40,7 +40,7 @@ render(
40
40
  id="issue"
41
41
  value={issue}
42
42
  onChange={setIssue}
43
- placeholder="Buscar issue…"
43
+ placeholder="Buscar item…"
44
44
  options={[
45
45
  { value: '412', label: 'Ajustar microcopy do handoff', hint: 'SOF-412' },
46
46
  { value: '418', label: 'Preview da sessão cai após deploy', hint: 'SOF-418' },
@@ -69,7 +69,7 @@ render(
69
69
  id="agent-skills"
70
70
  value={skills}
71
71
  onChange={setSkills}
72
- placeholder="Adicionar skill…"
72
+ placeholder="Adicionar habilidade…"
73
73
  options={[
74
74
  { value: 'clean-code', label: 'clean-code' },
75
75
  { value: 'code-style', label: 'code-style' },
@@ -181,7 +181,7 @@ render(
181
181
  icon={<Ticket />}
182
182
  value={task}
183
183
  onChange={setTask}
184
- placeholder="Vincular task"
184
+ placeholder="Vincular tarefa"
185
185
  options={[
186
186
  { value: 'gra-2', label: 'Nova tela anotações', hint: 'GRA-2' },
187
187
  { value: 'gra-7', label: 'Filtro por filial', hint: 'GRA-7' },
@@ -227,8 +227,8 @@ function Demo() {
227
227
  value={values}
228
228
  onChange={setValues}
229
229
  options={[
230
- { value: 'developer', label: 'Developer' },
231
- { value: 'reviewer', label: 'Reviewer' },
230
+ { value: 'developer', label: 'Desenvolvedor' },
231
+ { value: 'reviewer', label: 'Revisor' },
232
232
  { value: 'designer', label: 'Designer' },
233
233
  ]}
234
234
  placeholder="Papéis…"
@@ -1,8 +1,8 @@
1
1
  ---
2
- title: Contexto & Variante
2
+ title: Contexto e variante
3
3
  ---
4
4
 
5
- # Contexto & Variante
5
+ # Contexto e variante
6
6
 
7
7
  Componentes semânticos separam significado de aparência. `context` responde por que o elemento
8
8
  recebe destaque; `variant` escolhe como esse significado aparece. Essa ordem evita que nomes como