@softize/opus 25.6.0 → 25.7.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 CHANGED
@@ -1,4 +1,4 @@
1
- # Changelog do @softize/opus
1
+ # Changelog
2
2
 
3
3
  O que muda em cada versão — e, quando quebra, **o que fazer**. Regras de leitura:
4
4
  minor = novidade compatível; major = breaking (a seção **Breaking** diz a migração).
@@ -7,6 +7,39 @@ 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
+ ## 25.7.0 — 2026-10-07
11
+
12
+ Portais de documentação podem manter o mesmo mapa lateral em guias, ADRs e changelog. A superfície
13
+ leve `@softize/opus/ui/docs/browser` passa a oferecer `DocHeader`, `DocCatalog`, `DocRecordDetail`
14
+ e parsers de decisões e versões, sem carregar o catálogo do SDK nem o compilador de exemplos.
15
+ O host continua responsável por fontes, rotas e autorização.
16
+
17
+ `DocBrowser` aceita busca externa controlada, largura inicial da lateral, grupos inicialmente
18
+ recolhidos, destinos globais, páginas fora da árvore e conteúdo do host no lugar de um guia. O modo
19
+ editorial acrescenta contexto, sumário dos títulos renderizados e continuidade entre páginas.
20
+ A busca revela resultados sem perder a escolha manual dos ramos abertos. As novas propriedades
21
+ são opcionais: os consumidores existentes preservam a composição e os defaults anteriores.
22
+
23
+ O cabeçalho compartilhado mantém a busca documental compacta e centralizada. Os catálogos têm
24
+ busca textual própria dentro da página, independente do topo. ADRs apresentam contexto curto;
25
+ changelog mantém o texto das versões e destaca cuidados de atualização. Não há tabela, filtros,
26
+ controles de ordenação ou paginação nesses índices estáticos.
27
+
28
+ A adaptação à largura da tela é restrita ao cabeçalho, à leitura editorial e aos catálogos do
29
+ portal documental. A UI das aplicações e os componentes demonstrados nos exemplos vivos continuam
30
+ com o contrato desktop a partir de `64rem` da ADR 0017.
31
+
32
+ O portal do Opus passa a abrir pela documentação, com Visão geral, Instalação e Primeiros passos
33
+ antes das referências. ADRs e Changelog entram na lateral; os endereços anteriores dos guias e do
34
+ catálogo de UI são preservados. Não há migração obrigatória para quem não adotar essas composições.
35
+
36
+ Links de `SidebarItem` podem declarar `href`, com o tratamento de foco dos botões e abertura em
37
+ outra aba; sem esse campo, o item continua sendo um botão. A consulta do topo preserva a posição
38
+ de leitura dos registros. O changelog limita os cuidados de atualização à seção `Breaking`, sem
39
+ incluir novidades ou correções seguintes; resumos e detalhes preservam a ordem original das seções.
40
+ O changelog embutido usa **Breaking: cuidados ao atualizar** no lugar de **Breaking — o que fazer**.
41
+ O catálogo usa esse mesmo rótulo.
42
+
10
43
  ## 25.6.0 — 2026-10-07
11
44
 
12
45
  Aplicações que apresentam Markdown do próprio produto podem usar
@@ -2,6 +2,7 @@
2
2
 
3
3
  - **Status:** aceita.
4
4
  - **Data:** 2026-09-15.
5
+ - **Emenda:** 2026-10-07; portal documental delimitado pela ADR 0057.
5
6
 
6
7
  ## Contexto
7
8
 
@@ -28,6 +29,22 @@ Footers de formulário modal dimensionam as ações pelo conteúdo e usam `Cance
28
29
  `distribution="equal"` permanece uma escolha explícita para decisões de peso equivalente e,
29
30
  nesse caso, o cancelamento usa `outline`.
30
31
 
32
+ ### Exceção: portal de documentação
33
+
34
+ O portal documental tem uma jornada de leitura distinta da UI operacional das aplicações. Conforme
35
+ a ADR 0057, `DocHeader`, a leitura editorial de `DocBrowser` e `DocCatalog`/`DocRecordDetail`
36
+ podem adaptar cabeçalho, espaçamento e sumário à largura da tela. A exceção foi aprovada para
37
+ preservar a composição validada nos portais do Opus e da Grand Brasil.
38
+
39
+ A exceção não torna responsivos `Split`, `Sidebar`, formulários, tabelas ou outros componentes da
40
+ UI das aplicações. O host documental continua responsável pela composição de sua moldura em tela
41
+ estreita. Exemplos vivos usam o mesmo contrato desktop dos componentes demonstrados; o portal não
42
+ promete que todos os exemplos sejam adequados a telas menores que `64rem`.
43
+
44
+ O gate permite variantes de viewport somente nos três arquivos documentais identificados na ADR
45
+ 0057. Arquivos novos, mesmo dentro de `docs/`, continuam sujeitos à regra até uma decisão explícita.
46
+ Container queries não entram na exceção.
47
+
31
48
  ## Consequências
32
49
 
33
50
  - O mesmo componente preserva a composição a partir de `64rem`, inclusive dentro de panes,
@@ -50,5 +67,7 @@ nesse caso, o cancelamento usa `outline`.
50
67
  ## Verificação
51
68
 
52
69
  - Um teste de inventário reprova variantes de largura e container queries na UI viva.
70
+ - O mesmo teste delimita a exceção documental por arquivo e comprova que ela não alcança componentes
71
+ da aplicação, outros arquivos documentais ou container queries.
53
72
  - Testes estruturais fixam layouts, larguras de modal e distribuição dos footers.
54
73
  - Documentação pública declara a largura mínima, o default por conteúdo e a exceção `equal`.
@@ -0,0 +1,152 @@
1
+ # ADR 0057 — Portal documental compartilha navegação, busca e leitura
2
+
3
+ - **Status:** aceita para implementação local; publicação pendente.
4
+ - **Data:** 2026-10-07.
5
+
6
+ ## Contexto
7
+
8
+ Portais de documentação precisam apresentar guias, decisões e entregas sem trocar o mapa de
9
+ navegação a cada destino. O `DocBrowser` já organiza páginas, busca e árvore, mas sua composição
10
+ original mantém o campo na lateral e não permite usar o cabeçalho ou substituir a leitura por um
11
+ catálogo. Os portais do Opus e da Grand Brasil precisam dessas variações.
12
+
13
+ Copiar a árvore, a filtragem e o grid em cada aplicação cria comportamentos concorrentes e repete
14
+ defeitos de layout. Incorporar marca, rotas e autorização ao navegador, por outro lado, faria o SDK
15
+ assumir decisões do produto hospedeiro. A extensão precisa compartilhar a composição sem transferir
16
+ essas responsabilidades.
17
+
18
+ ## Decisão vigente
19
+
20
+ A superfície leve `@softize/opus/ui/docs/browser` oferece navegação, cabeçalho e leitura para fontes
21
+ fornecidas pelo host, sem importar o catálogo do Opus nem o compilador de exemplos vivos. A entrada
22
+ histórica `@softize/opus/ui/docs` mantém o catálogo completo e reexporta a mesma composição.
23
+
24
+ ### Cabeçalho e busca documental
25
+
26
+ `DocHeader` recebe marca, busca, ações e navegação opcional. Centraliza a geometria do topo, com
27
+ busca compacta de até `22rem`, colunas simétricas nas extremidades e uma linha própria para o campo
28
+ em telas menores que `64rem`. Não conhece tema, repositório, rotas ou estado de pesquisa.
29
+
30
+ `DocBrowser.search` mantém duas composições:
31
+
32
+ - `placement: "sidebar"` preserva o campo e o estado local existentes.
33
+ - `placement: "external"` recebe o valor controlado pelo host, filtra a mesma árvore e não
34
+ renderiza um segundo campo.
35
+
36
+ Nos dois portais, a busca documental mantém rótulo, apresentação, valor e função em guias,
37
+ catálogos e registros. Pesquisa os guias na lateral; não se transforma silenciosamente numa busca
38
+ de ADRs ou versões.
39
+
40
+ ### Hierarquia e leitura
41
+
42
+ `sidebarSize` configura a largura inicial usando o `PaneSize` do `Split`. A omissão preserva
43
+ `14rem`. `sidebarInitiallyCollapsed` permite começar com seções e grupos nomeados recolhidos,
44
+ inclusive quando a pessoa abre uma página por URL direta. A busca revela os resultados
45
+ temporariamente; limpá-la restaura os ramos abertos manualmente.
46
+
47
+ `sidebarLinks` apresenta destinos globais após os capítulos, no mesmo nível visual e fora da
48
+ sequência anterior/próxima. `active` identifica o catálogo atual. `navigationTitle` oferece um
49
+ rótulo curto para uma página sem alterar seu título completo ou os termos pesquisáveis.
50
+
51
+ Esses destinos usam `SidebarItem.href`, não uma cópia do estilo da linha. A extensão preserva o
52
+ botão quando `href` é omitido, o foco nos dois modos e a abertura nativa em outra aba. Quando há
53
+ `onClick`, somente a navegação simples é interceptada; o host mantém a decisão de roteamento.
54
+
55
+ O modo opcional `editorial` acrescenta contexto, sumário dos headings realmente renderizados e
56
+ links de continuidade segundo a ordem declarada. `additionalPages` preserva URLs de páginas fora
57
+ da árvore e da sequência. O renderer de exemplos aceita `codeCollapsible={false}` quando ocultar
58
+ o código interromperia uma explicação didática.
59
+
60
+ ### Catálogos e registros
61
+
62
+ `content` substitui a área de leitura sem desmontar a árvore. Quando esse slot é fornecido, o
63
+ host resolve rota e título; o navegador não escolhe uma página dos guias nem aplica seus redirects.
64
+ `null` representa uma leitura intencionalmente vazia. `sidebar={false}` permite atender
65
+ consumidores que precisem de uma página isolada, mas os dois portais mantêm a lateral em
66
+ guias, catálogos e registros.
67
+
68
+ `DocCatalog` oferece leitura editorial de registros estáticos, sem tabela, filtros, ordenação
69
+ configurável ou paginação. ADRs apresentam número, título, metadata declarada e o primeiro
70
+ parágrafo do contexto. Changelog apresenta texto integral por versão, com data e cuidados de
71
+ atualização em destaque. Os registros mais recentes aparecem primeiro.
72
+
73
+ Cada catálogo tem um campo textual na própria página. `query` e `onQueryChange` são controlados
74
+ pelo host, independentemente da busca documental. Os portais preservam a consulta local ao abrir
75
+ um registro e voltar, e a limpam ao trocar de catálogo; não limpam a consulta do topo pela navegação.
76
+
77
+ `DocRecordDetail` apresenta somente o nome do catálogo antes do título, sem breadcrumb ou retorno
78
+ duplicado. A navegação permanece na lateral. Para preservar os ramos abertos, o host mantém a mesma
79
+ instância de `DocBrowser` entre guia, catálogo e registro.
80
+
81
+ Os parsers projetam as mesmas fontes Markdown, sem cadastro editorial paralelo. Não inferem data
82
+ ou status ausentes, resumo sem seção de contexto ou releases anteriores ao versionamento.
83
+ Ressalvas do status permanecem no texto integral da ADR.
84
+
85
+ Este índice estático é uma exceção deliberada ao uso de `ActionList`: publica arquivos versionados
86
+ e funciona sem API da aplicação. Não substitui coleções de recursos servidas por actions.
87
+
88
+ ### Relação com o contrato desktop da UI
89
+
90
+ A composição documental é uma exceção explícita à ADR 0017: `DocHeader.tsx`, `DocBrowser.tsx`
91
+ (na leitura editorial) e `DocCatalog.tsx` (incluindo `DocRecordDetail`) podem usar variantes de
92
+ viewport para organizar cabeçalho, sumário e espaçamento. A exceção fica restrita a esses arquivos;
93
+ não libera toda a pasta `docs/` nem container queries. O gate verifica as funções de composição
94
+ permitidas, sem liberar outras funções ou a montagem de `Split` no mesmo arquivo.
95
+
96
+ Essa adaptação foi aprovada para o portal de leitura, não para a UI das aplicações. O host organiza
97
+ sua moldura em telas estreitas; `Split`, `Sidebar` e os exemplos vivos não ganham um contrato
98
+ responsivo por estarem dentro da documentação. Manter o portal inteiramente desktop descartaria
99
+ a composição já validada; ampliar a exceção para toda a UI alteraria uma decisão fora deste escopo.
100
+
101
+ ## Alternativas consideradas
102
+
103
+ ### Manter campo, largura e leitura fixos
104
+
105
+ Preservaria uma API menor, mas obrigaria portais autônomos a copiar a árvore ou adotar a composição
106
+ do catálogo embutido. As novas propriedades são opcionais e mantêm os defaults existentes.
107
+
108
+ ### Compor todo o portal dentro de DocBrowser
109
+
110
+ Centralizaria a implementação, ao custo de misturar identidade visual, navegação global,
111
+ autorização e rotas do host com a leitura documental. `DocHeader` compartilha somente a geometria;
112
+ `content` compartilha o mapa sem decidir os destinos.
113
+
114
+ ### Copiar grids e filtragem nos consumidores
115
+
116
+ Evitaria a extensão do pacote, mas os primeiros consumidores já repetiram um desalinhamento do
117
+ cabeçalho. `PageHeader` e `PageShell` organizam páginas de recursos, não o topo de um portal
118
+ documental. Manter essas cópias deixaria o mesmo defeito com duas correções locais.
119
+
120
+ ### Tabela para decisões e entregas
121
+
122
+ Foi implementada no protótipo com filtro, ordenação e paginação, mas rejeitada na avaliação visual.
123
+ ADRs precisam de contexto para escolher a leitura; changelog precisa manter a continuidade do texto.
124
+ A busca textual atende a consulta sem acrescentar controles de coleção.
125
+
126
+ ### Ocultar a lateral e trocar o alcance da busca no catálogo
127
+
128
+ Também foi experimentado e substituído após avaliação. Ocultar a lateral interrompia o mapa;
129
+ trocar o alcance do campo superior tornava a pesquisa ambígua. A decisão vigente mantém a lateral
130
+ e separa a consulta documental da consulta local.
131
+
132
+ ## Consequências
133
+
134
+ - Consumidores existentes preservam campo lateral, largura de `14rem` e composição não editorial.
135
+ - A biblioteca mantém uma árvore e uma regra de busca; o host mantém conteúdo, rotas e autorização.
136
+ - Os dois portais compartilham cabeçalho e catálogos sem compartilhar sua taxonomia específica.
137
+ - Guias, índices e fontes acessíveis por URL direta precisam ser filtrados antes de chegar ao
138
+ navegador quando houver restrição de acesso. Esta composição não é uma fronteira de segurança.
139
+ - A consolidação é um candidato local. A publicação e a adoção pela Grand Brasil são etapas
140
+ distintas, posteriores à revisão independente.
141
+
142
+ ## Verificação
143
+
144
+ - Testes de `DocBrowser`: busca local e externa, vazio, largura configurável, lateral opcional,
145
+ persistência de grupos, expansão temporária, conteúdo do host, redirects e leitura editorial.
146
+ - Testes de `DocHeader`: slots, atributos nativos e composição com ou sem busca e navegação.
147
+ - Testes de `DocCatalog`: busca local controlada, independência do topo, ausência de controles de
148
+ coleção, texto integral das versões, incompatibilidades e abertura nativa em outra aba.
149
+ - Testes dos parsers: metadata ausente, resumo canônico, acentos e termos combinados, texto integral
150
+ e ausência de versões retroativas fabricadas.
151
+ - Inspeção dos dois portais: largura estreita, alinhamento do topo, lateral persistente, busca,
152
+ URLs diretas, ida ao registro e retorno ao catálogo.
@@ -0,0 +1,12 @@
1
+ ---
2
+ title: Decisões arquiteturais
3
+ order: -1
4
+ ---
5
+ # Decisões arquiteturais
6
+
7
+ As ADRs registram o contexto, as alternativas e as consequências das decisões estruturais do
8
+ Opus. Use a Documentação para compreender o contrato vigente; consulte esta área quando precisar
9
+ entender por que uma restrição existe ou como ela evoluiu.
10
+
11
+ Uma ADR preserva seu contexto histórico mesmo quando é substituída. O comportamento vigente deve
12
+ estar documentado na superfície correspondente, sem exigir a reconstrução dessa sequência.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "25.6.0",
3
+ "version": "25.7.0",
4
4
  "description": "End-to-end action protocol for TypeScript. Single package with subpath exports (core + adapters).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -77,7 +77,9 @@ export interface SidebarItemProps {
77
77
  /** Complemento do tooltip no rail recolhido. */
78
78
  tooltipHint?: ReactNode;
79
79
  className?: string;
80
- onClick: () => void;
80
+ /** Destino nativo. Com onClick, intercepta somente a navegação simples; preserva novas abas. */
81
+ href?: string;
82
+ onClick?: () => void;
81
83
  }
82
84
 
83
85
  /** Linha única de navegação: o alvo principal e seus controles são irmãos, nunca botões aninhados. */
@@ -95,8 +97,29 @@ export function SidebarItem({
95
97
  tooltipHint,
96
98
  className,
97
99
  onClick,
100
+ href,
98
101
  }: SidebarItemProps): React.ReactElement {
99
102
  const collapsed = useSidebarCollapsed();
103
+ const Target = href !== undefined && !disabled ? "a" : "button";
104
+ const targetProps =
105
+ Target === "a" ? { href } : { type: "button" as const, disabled };
106
+ const activate = (
107
+ event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>,
108
+ ) => {
109
+ if (href !== undefined) {
110
+ if (
111
+ event.button !== 0 ||
112
+ event.metaKey ||
113
+ event.ctrlKey ||
114
+ event.shiftKey ||
115
+ event.altKey ||
116
+ !onClick
117
+ )
118
+ return;
119
+ event.preventDefault();
120
+ }
121
+ onClick?.();
122
+ };
100
123
  const hasActions = actions !== undefined && actions !== null;
101
124
  // O rótulo cortado esmaece em vez de terminar em reticências; a medição do transbordo
102
125
  // mantém a máscara fora do rótulo que cabe inteiro na linha.
@@ -104,12 +127,11 @@ export function SidebarItem({
104
127
  useOverflowing<HTMLSpanElement>(label);
105
128
  if (collapsed) {
106
129
  const button = (
107
- <button
108
- type="button"
109
- disabled={disabled}
130
+ <Target
131
+ {...targetProps}
110
132
  aria-label={label}
111
133
  aria-current={active ? "page" : undefined}
112
- onClick={onClick}
134
+ onClick={activate}
113
135
  className={cn(
114
136
  "mx-auto grid size-10 place-items-center rounded-md transition-colors disabled:pointer-events-none disabled:opacity-40",
115
137
  "outline-none",
@@ -125,7 +147,7 @@ export function SidebarItem({
125
147
  >
126
148
  {icon}
127
149
  </span>
128
- </button>
150
+ </Target>
129
151
  );
130
152
  // `className` vai no MESMO elemento nos dois modos — a linha —, senão a prop mudaria de alvo
131
153
  // conforme o rail estivesse recolhido, e o consumidor não teria como saber o que estilizou.
@@ -209,11 +231,10 @@ export function SidebarItem({
209
231
  )}
210
232
  </span>
211
233
  )}
212
- <button
213
- type="button"
214
- disabled={disabled}
234
+ <Target
235
+ {...targetProps}
215
236
  aria-current={active ? "page" : undefined}
216
- onClick={onClick}
237
+ onClick={activate}
217
238
  className={cn(
218
239
  "flex min-w-0 flex-1 items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left text-sm disabled:pointer-events-none",
219
240
  "outline-none",
@@ -254,7 +275,7 @@ export function SidebarItem({
254
275
  {badge}
255
276
  </span>
256
277
  )}
257
- </button>
278
+ </Target>
258
279
  {hasActions && (
259
280
  <div
260
281
  data-slot="sidebar-item-actions"