@softize/opus 12.7.1 → 12.8.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 +67 -0
- package/bin/lib/gen-dicts.mjs +11 -1
- package/bin/lib/gen-runner.mjs +8 -2
- package/bin/lib/materialize.mjs +5 -2
- package/docs/adr/0003-dictionary-presentation-is-declared.md +160 -0
- package/package.json +1 -1
- package/registry/skills/build-opus-ui/SKILL.md +31 -10
- package/registry/skills/build-opus-ui/references/evaluations.md +25 -0
- package/registry/skills/build-opus-ui/references/ui-patterns.md +85 -0
- package/registry/skills/implement-opus-change/SKILL.md +4 -3
- package/registry/skills/model-opus-dictionary/SKILL.md +76 -0
- package/registry/skills/model-opus-dictionary/agents/openai.yaml +4 -0
- package/registry/skills/model-opus-dictionary/references/evaluations.md +18 -0
- package/src/core/dictionary.ts +152 -0
- package/src/core/index.ts +18 -0
- package/src/core/types.ts +9 -1
- package/src/schema/drivers/zod.ts +46 -13
- package/src/ui/components/patterns/list.tsx +136 -51
- package/src/ui/components/primitives/badge.tsx +3 -0
- package/src/ui/components/primitives/detail.tsx +12 -1
- package/src/ui/components/primitives/dictionary-value.tsx +141 -0
- package/src/ui/components/primitives/empty-value.tsx +50 -0
- package/src/ui/components/primitives/pagination.tsx +86 -56
- package/src/ui/docs/content/action-list-dialog.md +1 -1
- package/src/ui/docs/content/action-list.md +34 -9
- package/src/ui/docs/content/badge.md +6 -3
- package/src/ui/docs/content/detail.md +19 -2
- package/src/ui/docs/content/dictionary-value.md +120 -0
- package/src/ui/docs/content/empty-value.md +48 -0
- package/src/ui/docs/content/pagination.md +54 -17
- package/src/ui/docs/doc-client.tsx +21 -4
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/meta.ts +14 -2
- package/src/ui/react.tsx +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,73 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
|
|
|
7
7
|
`opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
|
|
8
8
|
mudança cobra do seu código.
|
|
9
9
|
|
|
10
|
+
## 12.8.0 — 2026-09-02
|
|
11
|
+
|
|
12
|
+
A apresentação de um dicionário passa a ser declarada no próprio `t.dict`, e não inferida por
|
|
13
|
+
quem desenha a tela (ADR 0003). `t.dict(entries, { presentation })` aceita `classification`,
|
|
14
|
+
`status`, `stage` ou `plain`; cada entrada pode declarar `tone` (`neutral` · `info` · `success` ·
|
|
15
|
+
`warning` · `danger`), `icon` (nome do catálogo `iconPickerIcons`) e `description` (texto curto
|
|
16
|
+
para a pessoa, usado em tooltip). `doc` continua sendo o entendimento de negócio para manifest e
|
|
17
|
+
Lens. `presentation` inválido, `tone` inválido ou `tone` fora de status e estágio falham na
|
|
18
|
+
construção do dicionário. O manifest projeta
|
|
19
|
+
`presentation` ao lado de `doc`, e os stubs de `opus gen` preservam as opções.
|
|
20
|
+
|
|
21
|
+
`@softize/opus/ui/react` exporta `DictionaryValue`, o renderer compartilhado: classificação vira
|
|
22
|
+
`Badge` `outline`, status e estágio viram `Badge` tonal pelo `tone` da entrada (`neutral` sem tom,
|
|
23
|
+
nunca `outline`), `plain` ou sem papel vira texto. Ícone só quando declarado e presente no
|
|
24
|
+
catálogo; tooltip só quando `description` acrescenta ao rótulo; o texto está sempre presente.
|
|
25
|
+
Overrides explícitos por `presentation`, `variant`, `icon`, `icons`, `tooltip` e `fallback`. O
|
|
26
|
+
core ganha `presentDictionaryValue` e `dictionaryDescriptor` (sem React) e o tipo
|
|
27
|
+
`DictEntryMeta` passa a morar em `@softize/opus/core`, reexportado pelo driver Zod. `Badge` ganha
|
|
28
|
+
a variante tonal `danger`.
|
|
29
|
+
|
|
30
|
+
`ActionList` reconhece colunas de dicionário sem configuração duplicada: campo de saída com a meta
|
|
31
|
+
de `t.dict` renderiza `DictionaryValue`, e `ListColumnSpec.dictionary` nomeia um dicionário do
|
|
32
|
+
provider para campos de string comum. `cells` continua vencendo. Dicionários sem `presentation`
|
|
33
|
+
continuam como texto; a única mudança sem opt-in é que uma coluna de dicionário sem célula custom
|
|
34
|
+
passa a mostrar o rótulo em vez do código, inclusive no chip de `type: 'badge'`.
|
|
35
|
+
|
|
36
|
+
Valor ausente ganha representação padrão. `EmptyValue` mostra o travessão com “Não informado” à
|
|
37
|
+
leitura assistiva em célula compacta e o rótulo em texto corrido; `label` dá o significado do
|
|
38
|
+
domínio. `isAbsentValue` define a regra: `null`, `undefined`, string vazia ou só espaços; `0`,
|
|
39
|
+
`false` e coleções vazias são valores. Colunas escalares de `ActionList` deixam de ficar em branco
|
|
40
|
+
e passam a usar essa representação, com `ListColumnSpec.empty` para o texto do domínio (array e
|
|
41
|
+
objeto sem `cells` seguem sem representação padrão); `DetailField` mostra “Não informado” ou
|
|
42
|
+
`empty`. Condicionais locais de `—` podem ser removidas.
|
|
43
|
+
|
|
44
|
+
`Pagination` deixa de ser byte-fiel ao shadcn: os números têm altura fixa e largura mínima
|
|
45
|
+
quadrada que cresce com os dígitos (`h-9 min-w-9 w-auto tabular-nums`), `page` dá o conteúdo e o
|
|
46
|
+
nome acessível “Página N”, sem `href` o link vira `<button>` com foco e `disabled`, e as setas
|
|
47
|
+
falam pt-BR (`label` localiza, `iconOnly` deixa só a seta quadrada, `iconClassName` dimensiona o
|
|
48
|
+
svg). `ActionList` compõe essa primitiva no rodapé e remove o paginador paralelo montado com
|
|
49
|
+
`Button`; 5726 e 5727 já não se sobrepõem. **Breaking:** o default de `size` em `PaginationLink`
|
|
50
|
+
passa de `icon` para `default`; quem dependia do quadrado fixo `size-9` num número declara
|
|
51
|
+
`size="icon"`. Quem usava `PaginationPrevious`/`PaginationNext` com o texto em inglês passa a ver
|
|
52
|
+
“Página anterior”/“Próxima página”, e a elipse deixa de carregar texto assistivo morto.
|
|
53
|
+
|
|
54
|
+
Selects inline de `ActionFilterBar` passam a ter largura fixa e previsível (`w-40`; lookup e
|
|
55
|
+
múltiplo `w-52`): a label trunca em vez de alargar o filtro e o valor selecionado trunca no
|
|
56
|
+
trigger, com a opção inteira na lista. O modal de filtros avançados continua com `w-full`.
|
|
57
|
+
|
|
58
|
+
As skills `model-opus-dictionary` e `build-opus-ui` passam a exigir a classificação do papel de
|
|
59
|
+
apresentação e trazem a tabela de decisão, exemplos e avaliações que reprovam badge indiscriminado,
|
|
60
|
+
dimensões independentes misturadas na mesma célula, status apresentado como classificação, tooltip
|
|
61
|
+
que repete o rótulo, ícone inventado, comunicação somente por cor ou ícone, fallback de ausência
|
|
62
|
+
repetido à mão, paginador paralelo, número de página preso a largura fixa e select inline
|
|
63
|
+
dimensionado pelo conteúdo.
|
|
64
|
+
|
|
65
|
+
## 12.7.2 — 2026-09-01
|
|
66
|
+
|
|
67
|
+
`opus check` deixa de exigir modo `755` exato nos executáveis de `.githooks/`. O Git rastreia só o
|
|
68
|
+
bit de execução, então um checkout com umask `002` nasce com `775` e o gate acusava drift onde não
|
|
69
|
+
havia — um falso vermelho em toda worktree nova. O check agora aceita qualquer modo executável pelo
|
|
70
|
+
dono; o que o `opus setup` grava (criação e reparo) continua saindo com `755` canônico, mas um
|
|
71
|
+
executável existente com modo válido não é mais reescrito.
|
|
72
|
+
|
|
73
|
+
O registry inclui a skill `model-opus-dictionary`: modelagem de vocabulário fechado com `t.dict`,
|
|
74
|
+
schema derivado por `.zod()`, registro em `defineDomain({ dicts })` e a decisão dicionário ×
|
|
75
|
+
lookup × discriminante local. `implement-opus-change` e `build-opus-ui` passam a rotear para ela.
|
|
76
|
+
|
|
10
77
|
## 12.7.1 — 2026-09-01
|
|
11
78
|
|
|
12
79
|
No tema escuro, o item ativo de `Tabs` volta a usar uma superfície opaca, destacando a seleção
|
package/bin/lib/gen-dicts.mjs
CHANGED
|
@@ -102,10 +102,20 @@ function renderDict(name, dict) {
|
|
|
102
102
|
const entry = values[key]
|
|
103
103
|
lines.push(` ${formatKey(key)}: ${renderEntry(entry)},`)
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
const opts = renderDictOpts(dict)
|
|
106
|
+
lines.push(opts === null ? '})' : `}, ${opts})`)
|
|
106
107
|
return lines
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
/** Opções do dict (`doc`, `presentation`) — preservadas no stub pra que o front leia o
|
|
111
|
+
* mesmo papel de apresentação que o servidor declarou. */
|
|
112
|
+
function renderDictOpts(dict) {
|
|
113
|
+
const parts = []
|
|
114
|
+
if (typeof dict.doc === 'string') parts.push(`doc: ${JSON.stringify(dict.doc)}`)
|
|
115
|
+
if (typeof dict.presentation === 'string') parts.push(`presentation: ${JSON.stringify(dict.presentation)}`)
|
|
116
|
+
return parts.length === 0 ? null : `{ ${parts.join(', ')} }`
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
/**
|
|
110
120
|
* Emite a chave como identifier nu (`open`) quando for válido em TS,
|
|
111
121
|
* senão como literal string (`'foo-bar'`).
|
package/bin/lib/gen-runner.mjs
CHANGED
|
@@ -250,8 +250,14 @@ function serializeDicts(dicts) {
|
|
|
250
250
|
for (const k of keys) {
|
|
251
251
|
values[k] = entries[k] ?? null
|
|
252
252
|
}
|
|
253
|
-
// `doc` = entendimento do vocabulário inteiro (o que esse dict representa)
|
|
254
|
-
|
|
253
|
+
// `doc` = entendimento do vocabulário inteiro (o que esse dict representa);
|
|
254
|
+
// `presentation` = papel de apresentação declarado (ADR 0003) — null quando ausente.
|
|
255
|
+
out[name] = {
|
|
256
|
+
keys,
|
|
257
|
+
values,
|
|
258
|
+
doc: typeof params.doc === 'string' ? params.doc : null,
|
|
259
|
+
presentation: typeof params.presentation === 'string' ? params.presentation : null,
|
|
260
|
+
}
|
|
255
261
|
continue
|
|
256
262
|
}
|
|
257
263
|
// Fallback: dict-like com `keys()` + `metaFor()`.
|
package/bin/lib/materialize.mjs
CHANGED
|
@@ -355,7 +355,10 @@ function reconcileFile(root, destination, item, mode, errors, changes) {
|
|
|
355
355
|
if (currentFile === null) return
|
|
356
356
|
const executable = destination.startsWith('.githooks/')
|
|
357
357
|
const requiredMode = executable ? 0o755 : undefined
|
|
358
|
-
|
|
358
|
+
// O Git rastreia só o bit de execução; o modo efetivo vem do umask do checkout (755 com 022,
|
|
359
|
+
// 775 com 002). Exigir 755 exato reprovava worktrees legítimas — o check aceita qualquer modo
|
|
360
|
+
// executável pelo dono, e a correção continua gravando 755 canônico.
|
|
361
|
+
const modeIsCurrent = !executable || (currentFile.mode & 0o100) !== 0
|
|
359
362
|
const contentIsCurrent =
|
|
360
363
|
currentFile.exists &&
|
|
361
364
|
(executable ? currentFile.content === item.output : managedTextEquivalent(currentFile.content, item.output))
|
|
@@ -601,7 +604,7 @@ function reconcileSharedPrePush(root, mode, errors, changes) {
|
|
|
601
604
|
}
|
|
602
605
|
return
|
|
603
606
|
}
|
|
604
|
-
if ((current.mode &
|
|
607
|
+
if ((current.mode & 0o100) === 0) {
|
|
605
608
|
if (mode === 'check') errors.push(`${destination}: modo executável incorreto.`)
|
|
606
609
|
else if (replaceMaterializedFile(root, destination, expected, current, errors, { mode: 0o755 })) {
|
|
607
610
|
changes.push(`corrigido modo executável de ${destination}`)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# ADR 0003 — A apresentação de um dicionário é declarada, não inferida
|
|
2
|
+
|
|
3
|
+
- Status: aceita
|
|
4
|
+
- Data: 2026-09-02
|
|
5
|
+
|
|
6
|
+
## Contexto e forças
|
|
7
|
+
|
|
8
|
+
`t.dict` declara vocabulário fechado uma única vez: código estável, rótulo, documentação de
|
|
9
|
+
negócio e metadata livre por entrada. O manifest projeta esse vocabulário, a interface obtém
|
|
10
|
+
rótulos por `labelFor`/`metaFor` e os campos e filtros resolvem opções por
|
|
11
|
+
`options: { kind: 'dictionary', ref }` ou pela meta que viaja no schema do contrato.
|
|
12
|
+
|
|
13
|
+
O que o dicionário não declara é como o valor deve aparecer. Cada tela decide sozinha se o valor
|
|
14
|
+
vira texto, badge `outline`, badge tonal, ícone ou tooltip. Quando um agente gera a interface,
|
|
15
|
+
ele infere essa apresentação a partir do nome do dicionário e do gosto do momento. Na Grand
|
|
16
|
+
Brasil, “Pessoa física/Empresa” apareceu como texto secundário sob o nome enquanto
|
|
17
|
+
“Prospect/Cliente” apareceu como badge: duas dimensões independentes (tipo e estágio) foram lidas
|
|
18
|
+
como uma hierarquia, e a pessoa interpretou o tipo como explicação do estágio.
|
|
19
|
+
|
|
20
|
+
O problema não pertence a essa tela. Ele se repete em toda coleção com mais de um dicionário,
|
|
21
|
+
porque a única informação disponível para a interface é “este valor vem de um dicionário”, o que
|
|
22
|
+
não distingue classificação de status, nem status de estágio. As forças em tensão:
|
|
23
|
+
|
|
24
|
+
- o vocabulário precisa continuar declarado uma única vez, sem catálogo paralelo de cores e
|
|
25
|
+
ícones em cada tela;
|
|
26
|
+
- o core do Opus precisa continuar isomórfico e sem React, porque o contrato viaja para servidor,
|
|
27
|
+
manifest, Lens e agentes;
|
|
28
|
+
- dicionários existentes não podem mudar de aparência sem opt-in, e um dicionário sem metadata
|
|
29
|
+
precisa de um comportamento seguro;
|
|
30
|
+
- ícone e cor não podem ser a única forma de comunicar um valor;
|
|
31
|
+
- a orientação para agentes precisa ser verificável por avaliações, não apenas por prosa.
|
|
32
|
+
|
|
33
|
+
## Alternativas consideradas
|
|
34
|
+
|
|
35
|
+
### Manter a apresentação em cada tela
|
|
36
|
+
|
|
37
|
+
É o estado atual. Preserva flexibilidade total, mas mantém a inferência como regra: cada agente
|
|
38
|
+
decide de novo, e o mesmo dicionário aparece de formas diferentes em telas vizinhas. A observação
|
|
39
|
+
registrada na Grand Brasil é a segunda ocorrência do mesmo defeito em superfícies distintas.
|
|
40
|
+
Rejeitada.
|
|
41
|
+
|
|
42
|
+
### Inferir o papel pelo nome ou pelas chaves
|
|
43
|
+
|
|
44
|
+
Nomes como `Status` ou chaves como `open`/`closed` sugerem um status, mas `kind`, `type`,
|
|
45
|
+
`category` e `stage` não se distinguem por convenção de nome, e a inferência falha justamente nos
|
|
46
|
+
casos ambíguos que causaram o problema. Também moveria uma decisão de domínio para uma heurística
|
|
47
|
+
do SDK. Rejeitada.
|
|
48
|
+
|
|
49
|
+
### Tratar toda entrada de dicionário como badge
|
|
50
|
+
|
|
51
|
+
Resolve a inconsistência ao custo de ruído: tabelas densas ficam cheias de chips, e a diferença
|
|
52
|
+
entre classificação e estado desaparece. A convenção da casa reserva o badge tonal para estado
|
|
53
|
+
semântico. Rejeitada.
|
|
54
|
+
|
|
55
|
+
### Declarar o papel no dicionário e concentrar os defaults em um renderer
|
|
56
|
+
|
|
57
|
+
O dicionário declara seu papel de apresentação; cada entrada pode declarar tom, ícone e uma
|
|
58
|
+
descrição curta; um renderer compartilhado aplica os defaults e aceita override explícito. O
|
|
59
|
+
manifest projeta a metadata e a skill de UI orienta o agente a partir dela. Aceita.
|
|
60
|
+
|
|
61
|
+
## Decisão
|
|
62
|
+
|
|
63
|
+
### Contrato
|
|
64
|
+
|
|
65
|
+
O core ganha o módulo `dictionary` com o vocabulário fechado de apresentação, independente de
|
|
66
|
+
React:
|
|
67
|
+
|
|
68
|
+
- `DictPresentation = 'classification' | 'status' | 'stage' | 'plain'`, declarado no nível do
|
|
69
|
+
dicionário em `t.dict(entries, { presentation })`;
|
|
70
|
+
- `DictTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger'`, declarado por entrada em
|
|
71
|
+
`tone`, explícito e restrito a status e estágios;
|
|
72
|
+
- `icon` por entrada continua uma string, agora definida como identificador estável do catálogo
|
|
73
|
+
de ícones do Opus (`iconPickerIcons`); nome fora do catálogo não renderiza ícone;
|
|
74
|
+
- `description` por entrada é o texto curto voltado à pessoa, usado pelo tooltip. `doc` continua
|
|
75
|
+
sendo o entendimento de negócio para manifest, Lens e agentes e não vira tooltip.
|
|
76
|
+
|
|
77
|
+
`t.dict` valida `presentation` e `tone` em tempo de construção e falha com mensagem clara,
|
|
78
|
+
inclusive quando `tone` aparece fora de status e estágio. A meta
|
|
79
|
+
lógica passa a carregar `params.presentation`, e o manifest projeta `presentation` ao lado de
|
|
80
|
+
`doc`. O tipo `DictEntryMeta` migra para o core e é reexportado pelo driver Zod; a API pública não
|
|
81
|
+
muda.
|
|
82
|
+
|
|
83
|
+
### Defaults de apresentação
|
|
84
|
+
|
|
85
|
+
O core expõe `presentDictionaryValue(descriptor, value)`, a função pura que aplica a tabela de
|
|
86
|
+
decisão:
|
|
87
|
+
|
|
88
|
+
| Papel | Forma | Variante | Ícone | Tooltip |
|
|
89
|
+
| --- | --- | --- | --- | --- |
|
|
90
|
+
| `classification` | badge | `outline` | se declarado | se `description` acrescentar |
|
|
91
|
+
| `status` | badge | tonal por `tone`, `neutral` sem tom | se declarado | se `description` acrescentar |
|
|
92
|
+
| `stage` | badge | tonal por `tone`, `neutral` sem tom | se declarado | se `description` acrescentar |
|
|
93
|
+
| `plain` ou ausente | texto | — | se declarado | se `description` acrescentar |
|
|
94
|
+
|
|
95
|
+
Status e estágio nunca usam `outline`. Classificação não aceita `tone` (o construtor rejeita, e
|
|
96
|
+
metadata vinda de fora é ignorada), porque a diferença visual entre classificação e estado é o
|
|
97
|
+
que permite ler duas dimensões como independentes. Valor fora do dicionário é texto mesmo com
|
|
98
|
+
`variant` sobreposto. Tooltip só
|
|
99
|
+
existe quando `description` está presente e difere do rótulo. Valor fora do dicionário renderiza
|
|
100
|
+
o código como texto, sem badge, sem ícone e sem tooltip.
|
|
101
|
+
|
|
102
|
+
### Renderer
|
|
103
|
+
|
|
104
|
+
`@softize/opus/ui/react` exporta `DictionaryValue`, que recebe o dicionário e o valor e aplica os
|
|
105
|
+
defaults acima. Overrides são explícitos por props: `presentation`, `variant`, `icon`, `icons`,
|
|
106
|
+
`tooltip` e `fallback`. O rótulo é sempre renderizado como texto; o ícone é decorativo
|
|
107
|
+
(`aria-hidden`) e o badge com tooltip é focável para que a descrição alcance o teclado. O
|
|
108
|
+
`Badge` ganha a variante tonal `danger`, completando a família `success`/`warning`/`info` sem
|
|
109
|
+
reutilizar o `destructive` sólido.
|
|
110
|
+
|
|
111
|
+
### Colunas declarativas
|
|
112
|
+
|
|
113
|
+
`ActionList` passa a reconhecer que uma coluna do contrato mostra um dicionário por dois
|
|
114
|
+
caminhos, sem configuração duplicada:
|
|
115
|
+
|
|
116
|
+
1. zero-config: a coluna cujo campo no schema de saída carrega a meta de `t.dict` renderiza
|
|
117
|
+
`DictionaryValue` a partir dessa meta;
|
|
118
|
+
2. explícito: `ListColumnSpec.dictionary` nomeia a referência registrada em
|
|
119
|
+
`TbdlibProvider dicts`, para colunas cujo schema de saída é uma string comum.
|
|
120
|
+
|
|
121
|
+
`cells` continua vencendo qualquer coluna derivada. A única mudança de comportamento sem opt-in é
|
|
122
|
+
que uma coluna de dicionário sem célula custom passa a mostrar o rótulo em vez do código; a
|
|
123
|
+
apresentação visual (badge, tom, ícone, tooltip) exige `presentation` declarado. `type: 'badge'`
|
|
124
|
+
continua funcionando como chip `outline`, agora com o rótulo quando a coluna é dicionário.
|
|
125
|
+
|
|
126
|
+
### Orientação
|
|
127
|
+
|
|
128
|
+
A skill `model-opus-dictionary` passa a exigir a classificação do papel de apresentação junto com
|
|
129
|
+
a decisão dicionário × lookup × discriminante. A skill `build-opus-ui` ganha a tabela de decisão,
|
|
130
|
+
exemplos positivos e negativos e avaliações que reprovam badge indiscriminado, dimensões
|
|
131
|
+
misturadas na mesma célula, status apresentado como classificação, classificação tratada como
|
|
132
|
+
metadado irrelevante, tooltip que repete o rótulo, ícone inventado, comunicação somente por cor ou
|
|
133
|
+
ícone e excesso de badges em tabelas densas.
|
|
134
|
+
|
|
135
|
+
## Consequências
|
|
136
|
+
|
|
137
|
+
Dicionários existentes continuam renderizando como hoje até declararem `presentation`. Um
|
|
138
|
+
dicionário com `color` livre não recebe tom automático: `color` permanece metadata livre, lida
|
|
139
|
+
pela Lens, e o renderer honra apenas `tone`. Projetos que já usam `description` como texto de
|
|
140
|
+
apoio em opções passam a ter o mesmo campo alimentando o tooltip, o que é o uso pretendido.
|
|
141
|
+
|
|
142
|
+
O catálogo de ícones do Opus torna-se a fronteira do que um agente pode declarar. Vocabulário
|
|
143
|
+
fora dele exige extensão do catálogo ou `icons` no renderer, nunca um nome inventado.
|
|
144
|
+
|
|
145
|
+
A Lens ainda lê apenas `label` e `color` das entradas; projetar `presentation`, `tone` e `icon`
|
|
146
|
+
no inspetor é evolução separada. Selects de formulário continuam mostrando somente rótulos.
|
|
147
|
+
|
|
148
|
+
## Verificação
|
|
149
|
+
|
|
150
|
+
- testes do core para a tabela de decisão nos quatro papéis, tom, ícone, descrição igual ao
|
|
151
|
+
rótulo e valor desconhecido;
|
|
152
|
+
- testes de `t.dict` para validação de `presentation` e `tone` e para a meta projetada;
|
|
153
|
+
- testes de React para `DictionaryValue` cobrindo os quatro papéis, overrides, texto sempre
|
|
154
|
+
presente, ícone decorativo e tooltip focável;
|
|
155
|
+
- testes de `ActionList` para coluna zero-config, coluna com `dictionary` explícito, precedência
|
|
156
|
+
de `cells` e compatibilidade de `type: 'badge'`;
|
|
157
|
+
- teste de manifest provando `presentation` projetado e entradas com `tone`, `icon` e
|
|
158
|
+
`description`;
|
|
159
|
+
- validação das skills com casos positivo, negativo e de execução;
|
|
160
|
+
- typecheck, suíte do pacote, `opus check`, `opus copy --check` e gates de materialização.
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: build-opus-ui
|
|
3
3
|
description: Constrói interface contract-driven com hooks, forms, listas, views e componentes de @softize/opus/ui. Use ao implementar ou alterar telas que consomem actions Opus.
|
|
4
4
|
---
|
|
5
|
+
<!-- softize-skill-route: $model-opus-dictionary -->
|
|
5
6
|
|
|
6
7
|
# Construir UI Opus
|
|
7
8
|
|
|
@@ -14,33 +15,50 @@ existentes, mantendo navegação observável e componentes reutilizáveis sem la
|
|
|
14
15
|
|
|
15
16
|
1. Identificar o contrato e o kind da action; corrigir lacuna do contrato na fonte em vez
|
|
16
17
|
de compensá-la com tipo ou validação na UI.
|
|
17
|
-
2. Usar os hooks e componentes do catálogo `@softize/opus/ui/react` adequados ao kind.
|
|
18
|
-
|
|
18
|
+
2. Usar os hooks e componentes do catálogo `@softize/opus/ui/react` adequados ao kind. Rótulo e
|
|
19
|
+
mensagem de vocabulário fechado vêm do dicionário do domínio (`labelFor`/`metaFor`); ao
|
|
20
|
+
encontrar catálogo repetido à mão ou vocabulário ainda sem dicionário, carregar
|
|
21
|
+
`$model-opus-dictionary`.
|
|
22
|
+
3. Apresentar valor de dicionário por `DictionaryValue` ou pela coluna de `ActionList`, que
|
|
23
|
+
aplicam o papel declarado em `presentation` (classificação em badge `outline`, status e estágio
|
|
24
|
+
em badge tonal, `plain` em texto). Não escolher badge, tom ou ícone pelo nome do dicionário:
|
|
25
|
+
dicionário sem papel declarado renderiza texto e pede classificação por
|
|
26
|
+
`$model-opus-dictionary` antes de qualquer destaque visual. Sobrepor os defaults só com motivo
|
|
27
|
+
explícito nas props do renderer.
|
|
28
|
+
4. Dar a cada dimensão independente usada para comparação ou filtro um campo, coluna ou espaço
|
|
29
|
+
identificável próprio, com rótulo. Hierarquia tipográfica (texto secundário sob um nome) não
|
|
30
|
+
pode fazer uma dimensão parecer explicação de outra. Cor e ícone reforçam; o texto do valor
|
|
31
|
+
permanece sempre presente.
|
|
32
|
+
5. Deixar ausência, paginação e largura de filtro com o pattern: célula e `DetailField` já
|
|
33
|
+
representam valor ausente (`EmptyValue`, `empty` para o significado do domínio); listas
|
|
34
|
+
paginam pela primitiva `Pagination`; selects inline de filtro têm largura fixa. Não reescrever
|
|
35
|
+
esses defaults na tela.
|
|
36
|
+
6. Tratar página, filtro, seleção e modal importante como estado navegável por URL quando
|
|
19
37
|
o produto precisa de deep link, back/forward ou refresh.
|
|
20
|
-
|
|
38
|
+
7. Compor páginas com `Page` e seu teto centralizado padrão de `72rem`. Usar
|
|
21
39
|
`ContentHeader` em seções que precisam da mesma estrutura de título, descrição,
|
|
22
40
|
metadados e ações; ajustar `level` pela hierarquia semântica, não pelo destaque visual.
|
|
23
|
-
|
|
41
|
+
8. Manter margem e posicionamento no consumidor; componente reutilizável controla apenas
|
|
24
42
|
seu interior.
|
|
25
|
-
|
|
43
|
+
9. Não definir a fonte raiz em uma biblioteca ou componente. O navegador e a aplicação são
|
|
26
44
|
responsáveis por `font-size` em `html`; medidas escaláveis da UI usam `rem` ou a escala
|
|
27
45
|
relativa do Tailwind. Reservar `px` a hairlines e compensações presas à geometria da borda,
|
|
28
46
|
com justificativa e cobertura explícitas.
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
10. Evoluir um pattern compartilhado apenas quando a recorrência e o contrato estiverem claros.
|
|
48
|
+
11. Tratar tokens de superfície como pares indivisíveis no mesmo fragmento de classes:
|
|
31
49
|
`bg-card text-card-foreground` e `bg-popover text-popover-foreground`. Não depender da
|
|
32
50
|
igualdade atual com `--foreground`, porque o app pode sobrescrever cada par.
|
|
33
|
-
|
|
51
|
+
12. Usar diretamente a escala `rounded-*`; não criar radius por nome de componente quando
|
|
34
52
|
`rounded-xs` a `rounded-2xl` já expressam a forma. Escolher o degrau pela escala visual:
|
|
35
53
|
detalhes e itens internos tendem a `xs`/`sm`, controles e flutuantes compactos a `md`,
|
|
36
54
|
molduras estruturais a `lg`, e superfícies principais ou modais a `xl`. Essa associação
|
|
37
55
|
orienta o default, não cria uma restrição semântica. Em aninhamento, evitar moldura dupla e
|
|
38
56
|
reduzir o raio interno; em grupos conectados, remover os raios das arestas internas. Tamanho
|
|
39
57
|
e forma permanecem eixos separados; usar `shape="pill"` quando a pílula for intencional.
|
|
40
|
-
|
|
58
|
+
13. Distinguir uma região disponível para criação ou vínculo, representada por `Empty` com
|
|
41
59
|
moldura tracejada, de um resultado vazio dentro de uma estrutura existente, que preserva
|
|
42
60
|
a moldura sólida dessa estrutura.
|
|
43
|
-
|
|
61
|
+
14. Testar estados de loading, vazio, erro, sucesso, permissão e interação relevante.
|
|
44
62
|
|
|
45
63
|
## Verificação
|
|
46
64
|
|
|
@@ -58,6 +76,9 @@ Inspecionar visualmente a rota real e validar navegação por URL quando aplicá
|
|
|
58
76
|
- Não criar fetch, schema ou tipo paralelo ao contrato.
|
|
59
77
|
- Não copiar componente da lib para customizar sem antes verificar extensão/composição.
|
|
60
78
|
- Não forçar modal roteável quando o estado é efêmero e sem valor de navegação.
|
|
79
|
+
- Não inferir apresentação de dicionário: nem badge para todo valor, nem tom ou ícone
|
|
80
|
+
inventados, nem tooltip que repete o rótulo.
|
|
81
|
+
- Não repetir fallback de ausência, paginador ou largura de filtro que o pattern já resolve.
|
|
61
82
|
|
|
62
83
|
## Recursos
|
|
63
84
|
|
|
@@ -17,3 +17,28 @@
|
|
|
17
17
|
- Reprova: reconstruir manualmente o container de página, usar `Empty` tracejado como vazio de
|
|
18
18
|
tabela, alinhar toda coluna numérica à direita por inferência ou destacar “Cancelar” como ação
|
|
19
19
|
primária em modal.
|
|
20
|
+
- Execução de dicionários: montar a lista de clientes com “Pessoa física/Empresa” e
|
|
21
|
+
“Prospect/Cliente”; provar que o tipo ocupa a coluna “Tipo” como classificação (badge `outline`
|
|
22
|
+
com os ícones declarados `user` e `building`), que o estágio ocupa a coluna “Estágio” como badge
|
|
23
|
+
tonal sem `outline`, que nenhuma das duas usa `cells`, que o texto do valor está presente e que
|
|
24
|
+
não há tooltip em “Pessoa física/Empresa” quando a descrição não acrescenta ao rótulo.
|
|
25
|
+
- Reprova: envolver todo valor de dicionário em badge sem papel declarado, ou escolher a variante
|
|
26
|
+
pelo nome do dicionário.
|
|
27
|
+
- Reprova: colocar duas dimensões independentes na mesma célula sem identificação, como o tipo em
|
|
28
|
+
texto secundário sob o nome enquanto o estágio ganha badge.
|
|
29
|
+
- Reprova: apresentar status ou estágio com `outline`, ou tratar uma classificação como metadado
|
|
30
|
+
irrelevante em texto muted quando ela é dimensão de comparação ou filtro.
|
|
31
|
+
- Reprova: tooltip cuja descrição apenas repete o rótulo, ou `doc` de negócio exibido como tooltip.
|
|
32
|
+
- Reprova: ícone inferido ou inventado pela IA, fora do catálogo `iconPickerIcons` ou não declarado
|
|
33
|
+
na entrada do dicionário.
|
|
34
|
+
- Reprova: comunicar um valor somente por cor ou ícone (`Dot` sem texto, badge vazio).
|
|
35
|
+
- Reprova: tabela densa com badge em todas as colunas de dicionário quando só status e estágio
|
|
36
|
+
pedem destaque.
|
|
37
|
+
- Reprova: repetir à mão o fallback de valor ausente (`item.email === '' ? '—' : item.email`) em
|
|
38
|
+
células ou campos quando a coluna, o `DetailField` ou `EmptyValue` já representam a ausência.
|
|
39
|
+
- Reprova: montar um paginador paralelo com `Button` dentro de `ActionList` ou de outra lista em
|
|
40
|
+
vez de compor `Pagination`.
|
|
41
|
+
- Reprova: prender o número de página a largura fixa (`size-7 p-0`), fazendo 5726 e 5727 se
|
|
42
|
+
sobreporem.
|
|
43
|
+
- Reprova: dimensionar select inline de filtro pelo conteúdo ou pela label (`min-w-*` solto,
|
|
44
|
+
largura calculada), em vez da largura fixa do pattern.
|
|
@@ -42,4 +42,89 @@
|
|
|
42
42
|
Quando uma tabela, card ou grupo existente apenas não tem resultados, manter sua moldura
|
|
43
43
|
sólida e renderizar o vazio estrutural dentro dela; não trocar toda ausência de dados por
|
|
44
44
|
`Empty`.
|
|
45
|
+
- Valor de dicionário é apresentado por `DictionaryValue` ou pela coluna de `ActionList`; o
|
|
46
|
+
papel vem de `presentation` no `t.dict`, nunca do nome do dicionário nem do gosto da tela.
|
|
47
|
+
- Valor ausente (`null`, `undefined`, vazio, só espaços) tem representação padrão: coluna de
|
|
48
|
+
`ActionList` e `DetailField` já mostram travessão ou “Não informado”; o significado do domínio
|
|
49
|
+
entra por `ListColumnSpec.empty` ou `DetailField empty`, e renderer customizado usa
|
|
50
|
+
`EmptyValue`. `0` e `false` são valores. Não repetir a condicional de `—` em cada célula.
|
|
51
|
+
- Paginação é a primitiva `Pagination`, inclusive dentro de `ActionList`. Número de página tem
|
|
52
|
+
largura mínima e cresce com os dígitos; só as setas são quadradas. Não montar paginador com
|
|
53
|
+
`Button` nem prender números a `size-*`.
|
|
54
|
+
- Select inline de filtro tem largura fixa (`w-40`; lookup e múltiplo `w-52`): a label trunca, o
|
|
55
|
+
valor trunca no trigger e a opção inteira fica na lista. Não dimensionar filtro pelo conteúdo
|
|
56
|
+
nem pela label; o modal usa `w-full`.
|
|
45
57
|
- Catálogo e API efetivos vêm dos exports da versão instalada, não de memória ou exemplo antigo.
|
|
58
|
+
|
|
59
|
+
## Dicionários e dimensões
|
|
60
|
+
|
|
61
|
+
O dicionário declara o papel; a tela só escolhe onde o valor fica. Tabela de decisão aplicada
|
|
62
|
+
por `DictionaryValue` e pelas colunas de `ActionList`:
|
|
63
|
+
|
|
64
|
+
| Papel declarado | Exemplos | Forma | Variante | Ícone | Tooltip |
|
|
65
|
+
|---|---|---|---|---|---|
|
|
66
|
+
| `classification` | Tipo de cliente, categoria, natureza | Badge | `outline`; ignora `tone` | se a entrada declara `icon` do catálogo | se `description` acrescenta ao rótulo |
|
|
67
|
+
| `status` | Aberto, resolvido, degradado | Badge | tonal pelo `tone`; `neutral` sem tom; nunca `outline` | idem | idem |
|
|
68
|
+
| `stage` | Prospect, cliente; etapa do funil | Badge | igual a `status` | idem | idem |
|
|
69
|
+
| `plain` ou ausente | Fonte, formato, período | Texto | — | idem | idem |
|
|
70
|
+
|
|
71
|
+
Regras que não dependem da tabela:
|
|
72
|
+
|
|
73
|
+
- Dimensão independente usada para comparação ou filtro ocupa coluna, campo ou espaço
|
|
74
|
+
identificável próprio, com rótulo. Duas dimensões na mesma célula sem identificação viram
|
|
75
|
+
hierarquia falsa.
|
|
76
|
+
- O texto do valor está sempre presente; cor e ícone reforçam, não substituem.
|
|
77
|
+
- Tabela densa com várias colunas de dicionário mantém badge só nas dimensões de status e estágio;
|
|
78
|
+
classificações secundárias podem ficar em texto (`presentation="plain"` na ocorrência) quando o
|
|
79
|
+
excesso de chips prejudica a leitura.
|
|
80
|
+
- Sem `presentation` declarado, o valor é texto. A correção é classificar o dicionário
|
|
81
|
+
(`$model-opus-dictionary`), não escolher uma variante na tela.
|
|
82
|
+
|
|
83
|
+
Exemplo correto — tipo e estágio em colunas próprias, apresentação do dicionário:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
export const customerKindDict = t.dict(
|
|
87
|
+
{ pf: { label: 'Pessoa física', icon: 'user' }, pj: { label: 'Empresa', icon: 'building' } },
|
|
88
|
+
{ doc: 'Natureza da parte no cadastro global.', presentation: 'classification' },
|
|
89
|
+
)
|
|
90
|
+
export const customerStageDict = t.dict(
|
|
91
|
+
{ prospect: { label: 'Prospect' }, customer: { label: 'Cliente', tone: 'success' } },
|
|
92
|
+
{ doc: 'Estágio comercial atual da parte.', presentation: 'stage' },
|
|
93
|
+
)
|
|
94
|
+
// contrato:
|
|
95
|
+
// columns: [{ key: 'name' }, { key: 'kind', label: 'Tipo' }, { key: 'stage', label: 'Estágio' }]
|
|
96
|
+
// output: z.object({ kind: customerKindDict.zod(), stage: customerStageDict.zod() })
|
|
97
|
+
// tela: <ActionList action={customerListContract} input={{}} /> — sem cells para kind e stage
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Exemplos incorretos:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// Dimensões misturadas: o tipo vira "explicação" do nome; o estágio vira badge à mão.
|
|
104
|
+
cells={{
|
|
105
|
+
name: (item) => (
|
|
106
|
+
<div>
|
|
107
|
+
<p className="font-medium">{item.name}</p>
|
|
108
|
+
<span className="text-xs text-muted-foreground">
|
|
109
|
+
{item.kind === 'pj' ? 'Empresa' : 'Pessoa física'}
|
|
110
|
+
</span>
|
|
111
|
+
</div>
|
|
112
|
+
),
|
|
113
|
+
stage: (item) =>
|
|
114
|
+
item.stage === 'customer'
|
|
115
|
+
? <Badge variant="secondary">Cliente</Badge>
|
|
116
|
+
: <Badge variant="outline">Prospect</Badge>,
|
|
117
|
+
}}
|
|
118
|
+
|
|
119
|
+
// Badge para tudo: fonte, formato e período viram chips sem papel declarado.
|
|
120
|
+
<Badge variant="outline">{reportFormatDict.labelFor(item.format)}</Badge>
|
|
121
|
+
|
|
122
|
+
// Status apresentado como classificação (outline) e ícone inventado fora do catálogo.
|
|
123
|
+
<Badge variant="outline"><Sparkles /> {statusDict.labelFor(item.status)}</Badge>
|
|
124
|
+
|
|
125
|
+
// Tooltip que repete o rótulo; cor como único sinal.
|
|
126
|
+
<Tooltip>
|
|
127
|
+
<TooltipTrigger><Dot variant="success" /></TooltipTrigger>
|
|
128
|
+
<TooltipContent>Cliente</TooltipContent>
|
|
129
|
+
</Tooltip>
|
|
130
|
+
```
|
|
@@ -6,6 +6,7 @@ description: Implementa uma mudança em projeto baseado no Opus preservando cont
|
|
|
6
6
|
<!-- softize-skill-route: $test-opus-action -->
|
|
7
7
|
<!-- softize-skill-route: $build-opus-ui -->
|
|
8
8
|
<!-- softize-skill-route: $create-opus-seed -->
|
|
9
|
+
<!-- softize-skill-route: $model-opus-dictionary -->
|
|
9
10
|
|
|
10
11
|
# Implementar mudança Opus
|
|
11
12
|
|
|
@@ -26,9 +27,9 @@ dependências server-only, bindings registrados, testes e gates verdes.
|
|
|
26
27
|
2. Modelar primeiro o contrato observável: nome, descrição, input, output, erros e metadata.
|
|
27
28
|
3. Manter código compartilhável fora de banco, segredo, filesystem e drivers server-only.
|
|
28
29
|
4. Implementar o binding e registrar o `ActionDef` no domínio/runtime conforme a topologia local.
|
|
29
|
-
5. Carregar e seguir `$create-opus-action`, `$test-opus-action`, `$build-opus-ui
|
|
30
|
-
`$create-opus-seed` antes
|
|
31
|
-
|
|
30
|
+
5. Carregar e seguir `$create-opus-action`, `$test-opus-action`, `$build-opus-ui`,
|
|
31
|
+
`$create-opus-seed` ou `$model-opus-dictionary` antes do passo correspondente quando a
|
|
32
|
+
mudança entrar nesses workflows especializados.
|
|
32
33
|
6. Atualizar manifest, docs geradas e exemplos somente pelos comandos do repo.
|
|
33
34
|
|
|
34
35
|
## Verificação
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: model-opus-dictionary
|
|
3
|
+
description: Modela vocabulário fechado de domínio com t.dict, schema derivado e registro no domínio, decidindo entre dicionário, lookup e discriminante local. Use ao introduzir ou migrar códigos estáveis que contrato, regra, interface ou auditoria reutilizam.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Modelar dicionário Opus
|
|
7
|
+
|
|
8
|
+
## Resultado
|
|
9
|
+
|
|
10
|
+
Entregar o vocabulário declarado uma única vez: códigos estáveis com rótulo e documentação em
|
|
11
|
+
`t.dict`, schema derivado por `.zod()`, registro em `defineDomain({ dicts })` e consumidores lendo
|
|
12
|
+
da mesma instância, sem catálogo repetido em enum, opção estática ou formatador local.
|
|
13
|
+
|
|
14
|
+
## Entradas
|
|
15
|
+
|
|
16
|
+
- códigos, rótulos e significado de negócio do vocabulário, com quem os reutiliza;
|
|
17
|
+
- contrato, regra, interface ou auditoria que hoje repetem o catálogo;
|
|
18
|
+
- gates do projeto para representações legadas (baseline de `z.enum`/opções estáticas), quando
|
|
19
|
+
existirem.
|
|
20
|
+
|
|
21
|
+
## Procedimento
|
|
22
|
+
|
|
23
|
+
1. Classificar o vocabulário antes de codificar:
|
|
24
|
+
- **dicionário** — fechado nesta versão, códigos estáveis, significado de domínio reutilizado
|
|
25
|
+
por mais de um consumidor;
|
|
26
|
+
- **lookup** — administrável, pesquisável, autorizável ou vindo do banco: expor por action
|
|
27
|
+
`kind: 'list'`/lookup, nunca por opção estática;
|
|
28
|
+
- **discriminante local** — enum ou literal privado de transporte/controle que não repete um
|
|
29
|
+
catálogo apresentado nem uma regra compartilhada: permanece local e fora do dicionário.
|
|
30
|
+
2. Declarar o dicionário com `t.dict`, no módulo de dicionários do domínio: chave é código de
|
|
31
|
+
máquina persistível; rótulo, documentação e demais metadata evoluem sem migração de dados.
|
|
32
|
+
3. Classificar o papel de apresentação e declará-lo em `presentation`, porque a interface não o
|
|
33
|
+
infere:
|
|
34
|
+
- **classification** — tipo, categoria ou natureza: dimensão estável de comparação;
|
|
35
|
+
- **status** — situação operacional que muda com o tempo;
|
|
36
|
+
- **stage** — etapa de um ciclo ou funil;
|
|
37
|
+
- **plain** — valor que só precisa ser legível (o mesmo efeito de omitir).
|
|
38
|
+
Por entrada, declarar `tone` somente em status e estágio, `icon` somente com nome existente no
|
|
39
|
+
catálogo `iconPickerIcons`, e `description` somente quando acrescentar algo ao rótulo. `doc`
|
|
40
|
+
continua sendo o entendimento de negócio para manifest e Lens; não é tooltip.
|
|
41
|
+
4. Derivar o schema por `.zod()` e usá-lo em todo contrato que valide o código; não redeclarar a
|
|
42
|
+
união em `z.enum` nem em tipo literal paralelo.
|
|
43
|
+
5. Registrar o dicionário em `defineDomain({ dicts })` para que manifest e Lens o projetem.
|
|
44
|
+
6. Migrar os consumidores para a mesma instância: `DictionaryValue` ou a coluna de `ActionList`
|
|
45
|
+
para apresentar o valor, `labelFor`/`metaFor` nas mensagens; remover o catálogo repetido de
|
|
46
|
+
formatadores, selects, mapas de variante de badge e comparações.
|
|
47
|
+
7. Manter autorização e invariantes no servidor: metadata de dicionário orienta a interface, mas
|
|
48
|
+
não é barreira de integridade. Quando auditoria precisar preservar o que a pessoa viu,
|
|
49
|
+
armazenar o código e um snapshot do texto no momento do evento.
|
|
50
|
+
8. Ao reduzir uma representação legada coberta por baseline de gate, apertar o baseline no mesmo
|
|
51
|
+
diff. Ao introduzir uma nova ocorrência fora do dicionário, classificá-la e justificá-la no
|
|
52
|
+
próprio diff; atualizar baseline não é correção automática.
|
|
53
|
+
|
|
54
|
+
## Verificação
|
|
55
|
+
|
|
56
|
+
Rodar `opus check`, typecheck e os testes afetados; conferir no manifest que o domínio projeta o
|
|
57
|
+
dicionário com `presentation`; provar que o schema rejeita código fora do catálogo e que a
|
|
58
|
+
interface obtém rótulo e apresentação da mesma instância. Se a mudança alterou texto humano de
|
|
59
|
+
contrato ou componente mapeado, regenerar com `opus copy` e executar os checks de copy do projeto.
|
|
60
|
+
|
|
61
|
+
## Limites
|
|
62
|
+
|
|
63
|
+
- Não migrar todos os enums de uma vez; a adoção é incremental e cada ocorrência exige
|
|
64
|
+
classificação própria.
|
|
65
|
+
- Não transformar discriminante local legítimo em dicionário por semelhança superficial.
|
|
66
|
+
- Não usar tabela ou action de lookup para valores compilados e estáveis; nem dicionário para
|
|
67
|
+
dados administráveis de runtime.
|
|
68
|
+
- Não tratar metadata de dicionário como autorização ou invariante de integridade.
|
|
69
|
+
- Não omitir `presentation` esperando que a tela deduza o papel pelo nome do dicionário; sem
|
|
70
|
+
papel declarado, o valor é texto.
|
|
71
|
+
- Não declarar `tone` em classificação, nem `icon` fora do catálogo, nem `description` que
|
|
72
|
+
repita o rótulo.
|
|
73
|
+
|
|
74
|
+
## Recursos
|
|
75
|
+
|
|
76
|
+
- Use [avaliações](references/evaluations.md) ao evoluir esta skill.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Avaliações
|
|
2
|
+
|
|
3
|
+
- Positiva: “Os motivos de conflito de clientes aparecem repetidos no contrato, na UI e nas
|
|
4
|
+
mensagens; centralize esse vocabulário.” Deve disparar, declarar `t.dict`, derivar `.zod()`,
|
|
5
|
+
registrar em `defineDomain({ dicts })` e migrar os consumidores para a mesma instância.
|
|
6
|
+
- Negativa: “Adicione um campo de status interno `pending | done` ao envelope de transporte desta
|
|
7
|
+
fila.” Não deve disparar quando o par é discriminante privado que não repete catálogo
|
|
8
|
+
apresentado nem regra compartilhada.
|
|
9
|
+
- Execução: migrar um `z.enum` legado coberto por baseline, apertar o baseline no mesmo diff e
|
|
10
|
+
provar que manifest projeta o dicionário, que o schema rejeita código fora do catálogo e que a
|
|
11
|
+
interface lê rótulos por `labelFor`/`metaFor`.
|
|
12
|
+
- Execução de apresentação: dado um cadastro com “Pessoa física/Empresa” e “Prospect/Cliente”,
|
|
13
|
+
declarar o primeiro como `classification` com ícones `user` e `building` do catálogo e o segundo
|
|
14
|
+
como `stage` com `tone` explícito, sem `description` que repita o rótulo; provar que o manifest
|
|
15
|
+
projeta `presentation` e que `t.dict` rejeita `presentation` ou `tone` fora do vocabulário.
|
|
16
|
+
- Reprova: declarar `tone` em uma classificação, inventar `icon` fora do catálogo, copiar `doc`
|
|
17
|
+
para `description` ou deixar `presentation` ausente em um status esperando que a tela infira o
|
|
18
|
+
badge.
|