@softize/opus 27.1.0 → 29.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/bin/cli.mjs +68 -3
  3. package/bin/lib/check.mjs +139 -30
  4. package/bin/lib/copy.mjs +505 -67
  5. package/bin/lib/materialize.mjs +99 -1
  6. package/docs/adr/0015-action-size-follows-interaction-density.md +2 -0
  7. package/docs/adr/0028-schedules-run-actions-as-the-system-by-action-opt-in.md +1 -1
  8. package/docs/adr/0029-page-header-is-the-single-page-introduction.md +3 -1
  9. package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
  10. package/docs/adr/0060-surface-composition-separates-chrome-and-intro.md +161 -0
  11. package/docs/adr/0061-claude-md-imports-agents-md.md +41 -0
  12. package/docs/adr/README.md +4 -0
  13. package/docs/code-style.md +47 -8
  14. package/docs/protocol.md +1 -1
  15. package/docs/releasing.md +7 -0
  16. package/package.json +1 -1
  17. package/registry/instructions/opus.md +3 -1
  18. package/registry/skills/build-opus-ui/SKILL.md +16 -14
  19. package/registry/skills/build-opus-ui/references/evaluations.md +10 -0
  20. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -15
  21. package/registry/skills/maintain-opus-docs/SKILL.md +2 -1
  22. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +9 -0
  23. package/registry/skills/maintain-opus-docs/references/evaluations.md +8 -0
  24. package/registry/templates/app/pnpm-workspace.yaml +1 -1
  25. package/src/core/system-actor.ts +1 -1
  26. package/src/ui/components/patterns/list.tsx +63 -54
  27. package/src/ui/components/patterns/page.tsx +146 -38
  28. package/src/ui/components/patterns/presentation.tsx +44 -59
  29. package/src/ui/components/patterns/structural-header.tsx +42 -6
  30. package/src/ui/components/patterns/surface-anatomy.ts +52 -22
  31. package/src/ui/components/patterns/surface.tsx +2 -3
  32. package/src/ui/components/primitives/control.ts +2 -2
  33. package/src/ui/components/primitives/dialog.tsx +7 -2
  34. package/src/ui/components/primitives/drawer.tsx +7 -2
  35. package/src/ui/components/primitives/icon-picker.tsx +271 -34
  36. package/src/ui/components/primitives/pagination.tsx +5 -4
  37. package/src/ui/components/primitives/tabs.tsx +15 -2
  38. package/src/ui/components/primitives/toggle.tsx +1 -1
  39. package/src/ui/docs/content/action-list.md +12 -3
  40. package/src/ui/docs/content/actions.md +1 -1
  41. package/src/ui/docs/content/auth.md +3 -3
  42. package/src/ui/docs/content/button.md +7 -7
  43. package/src/ui/docs/content/cli.md +6 -0
  44. package/src/ui/docs/content/communication.md +10 -8
  45. package/src/ui/docs/content/compositions.md +35 -23
  46. package/src/ui/docs/content/dialog.md +18 -6
  47. package/src/ui/docs/content/drawer.md +17 -5
  48. package/src/ui/docs/content/events.md +13 -13
  49. package/src/ui/docs/content/icon-picker.md +37 -19
  50. package/src/ui/docs/content/input.md +1 -1
  51. package/src/ui/docs/content/installation.md +4 -0
  52. package/src/ui/docs/content/log.md +1 -1
  53. package/src/ui/docs/content/page.md +169 -69
  54. package/src/ui/docs/content/pagination.md +7 -8
  55. package/src/ui/docs/content/presentation.md +4 -3
  56. package/src/ui/docs/content/queue.md +2 -2
  57. package/src/ui/docs/content/router.md +1 -1
  58. package/src/ui/docs/content/scheduler.md +15 -15
  59. package/src/ui/docs/content/surface.md +8 -5
  60. package/src/ui/docs/content/tabs.md +12 -4
  61. package/src/ui/docs/content/testing.md +4 -4
  62. package/src/ui/meta.ts +1 -1
  63. package/src/ui/react.tsx +9 -1
  64. package/src/vite/design.ts +1 -1
@@ -9,24 +9,22 @@
9
9
  lista; não reconstruir essa decisão com condicionais no componente da página.
10
10
  - `Page` fornece o `<main>` e o container centralizado com teto padrão de `80rem`. Sua forma
11
11
  explícita exige `PageBody`, exatamente um heading principal e aceita `PageHeader` e `PageFooter`.
12
- Declare esse heading com `PageTitle` no header, `Surface level={1}` ou um `PageState` ativo no
13
- body. `PageHeader` é a única introdução estrutural e reúne navegação, título, `PageDescription`
14
- opcional e ações. `title`, `description` e `actions` no próprio `Page` abreviam o caso direto.
15
- Não misturar as duas formas. Alterar `className` apenas quando a superfície tiver uma necessidade
16
- real de largura; não reconstruir esse container em cada rota.
17
- - `PageHeader` declara navegação contextual, título, subheading opcional e ações. Em uma subpágina simples, `PageBack`
18
- recebe o destino pai explícito e aparece acima da linha do título como controle somente com
19
- ícone. Para mais de um ancestral relevante, use
20
- `Breadcrumb` dentro de `PageNavigation`. Não combine retorno e breadcrumb nem crie um chrome
21
- paralelo para uma `Page`. `PageActionsTarget` fica reservado a workspaces imersivos que já
22
- possuam chrome próprio.
12
+ Declare esse heading com PageTitle na PageIntro ou no PageHeader compacto, Surface level={1}
13
+ ou PageState ativo. A intro abre o body; ações locais usam PageIntroActions, enquanto PageActions
14
+ pertence ao header. A forma curta coloca title/description na intro e actions no header.
15
+ Não misturar as duas formas. Para ajustar a largura interna, usar contentClassName; className pertence ao container externo.
16
+ - Na composição com intro, PageHeader reúne navegação contextual e ações da superfície na mesma
17
+ linha. PageBack recebe o destino pai explícito; para vários ancestrais relevantes, usar Breadcrumb
18
+ em PageNavigation. Não combinar retorno e breadcrumb, nem criar chrome paralelo. Na composição
19
+ compacta, título e orientação permanecem no header. PageActionsTarget fica reservado a workspaces
20
+ imersivos com chrome próprio.
23
21
  - Quando shell e rota conhecem partes diferentes da mesma página, use `PageShell` ao redor da rota.
24
22
  O shell entrega navegação e ações ao `PageHeader` descendente sem criar uma barra visual.
25
- Ações com texto no cabeçalho usam o tamanho `default`, como as ações de footer de Dialog e Drawer;
26
- ações somente com ícone de chrome usam `icon`. O tamanho `sm` fica para ações operacionais em
23
+ Ações com texto no cabeçalho usam `sm`, e ações somente com ícone usam `icon-sm`.
24
+ Ações de footer de Dialog e Drawer conservam `default`. O tamanho `sm` fica para ações operacionais em
27
25
  toolbar, seção ou coleção densa, e `xs`/`icon-xs` para ações internas de linha ou célula. Contexto
28
26
  e variante resolvem a hierarquia visual sem alterar essa medida. Em uma coleção, a ação de
29
- criar usa botão textual `default`, sem ícone, no extremo oposto do `SurfaceHeader`. Não monte
27
+ criar usa botão textual `sm`, sem ícone, no extremo oposto do `SurfaceHeader`. Não monte
30
28
  cabeçalho paralelo, portal ou seletor global para reconstruir essa composição.
31
29
  - `PageState` substitui todo o conteúdo principal quando a página carrega, falha ou está vazia. Na
32
30
  forma curta ele pode ser escrito como filho direto de `Page`, que cria o `PageBody`; na forma
@@ -41,7 +39,7 @@
41
39
  SurfaceTitle, SurfaceDescription, SurfaceMeta, SurfaceActions) + SurfaceBody + SurfaceFooter`.
42
40
  Os slots nunca ficam soltos. `title`, `description`, `count`, `media` e `actions` são a abreviação
43
41
  para o caso direto e não podem ser misturados ao header explícito. Em `level={1}`, ações da
44
- coleção ficam no extremo oposto ao título; a criação usa botão textual `default`, sem ícone, com
42
+ coleção ficam no extremo oposto ao título; a criação usa botão textual `sm`, sem ícone, com
45
43
  `Criar recurso`.
46
44
  O diálogo de criação usa `Criar recurso` e o de edição, `Editar recurso`. `level` preserva a
47
45
  hierarquia semântica do heading. `Surface framed` aceita `layout="sectioned"`; declare
@@ -229,3 +227,7 @@ abaixo do controle. Reservar `FieldSpec.help` ao ícone com detalhe complementar
229
227
  Omitir repetição da label; combinar ambos somente com funções distintas. Regra de vários campos
230
228
  pertence ao corpo ou ao grupo. Em composição manual, `FieldDescription` e erro entram juntos no
231
229
  `aria-describedby` do controle.
230
+
231
+ - Manter header e footer de Page fora da rolagem vertical. PageBody é o viewport em flow; em fill,
232
+ repassar a altura à lista quando ela assume a rolagem. Não reconstruir a fixação com sticky ou
233
+ fixed nem tornar PageShell uma segunda região de rolagem vertical.
@@ -52,7 +52,8 @@ o código não sustenta.
52
52
 
53
53
  Executar `node scripts/audit-docs.mjs "$(git rev-parse --show-toplevel)"` a partir do diretório
54
54
  desta skill e, depois, os testes do renderer e do catálogo, typecheck, `opus check` e
55
- `opus copy --check`. Conferir
55
+ `opus copy --check`. No repositório do Opus, o teste `tests/scripts/example-language.test.ts`
56
+ reprova identificadores com palavra em português nos exemplos e nas fontes; rode-o também. Conferir
56
57
  que todos os previews renderizam, que cada API pública documentada possui sua própria referência,
57
58
  que nenhum arquivo publicado ficou órfão e que links e redirects chegam à página esperada. Fazer uma
58
59
  passada visual nas páginas alteradas e registrar qualquer limitação que o gate automático não cubra.
@@ -40,6 +40,13 @@ genérica.
40
40
  - Depois do código, explicar somente efeitos que não sejam óbvios pela leitura: default aplicado,
41
41
  estado controlado, ação automática, limite ou consequência.
42
42
  - Usar preview para comportamento visual e bloco de código para integração sem palco executável.
43
+ - Escrever o código dos exemplos em inglês, como o resto do código: variáveis, funções, campos,
44
+ nomes de action, domínios, eventos, schedules, permissões e caminhos de arquivo
45
+ (`ledgerCloseContract`, `ledger.close`, `ledger:close`, `design/scenarios/orders.ts`). Isso vale
46
+ também para o código em crase no meio da prosa. O texto que a pessoa vê — `label`, mensagens,
47
+ descrições, conteúdo JSX — e os comentários continuam em pt-BR. Um exemplo fictício não tem
48
+ vocabulário de domínio estabelecido, então a exceção de domínio das instruções do projeto não se
49
+ aplica a ele.
43
50
 
44
51
  ## Famílias e referência
45
52
 
@@ -74,6 +81,8 @@ Para cada título, parágrafo, item, exemplo e célula de tabela, verificar:
74
81
  8. O texto ao redor do exemplo explica intenção e comportamento, sem narrar cada linha do código?
75
82
  9. O título permite localizar a informação pela tarefa, inclusive por link direto?
76
83
  10. Pontuação, capitalização, concordância e paralelismo estão consistentes?
84
+ 11. Os identificadores do exemplo e do código em crase estão em inglês, e o texto exibido à pessoa,
85
+ em pt-BR?
77
86
 
78
87
  Ao terminar cada página, reler a sequência inteira. Linhas corretas isoladamente ainda podem formar
79
88
  uma página repetitiva, invertida ou sem progressão.
@@ -1,3 +1,5 @@
1
+ <!-- language-exempt-file: o caso de avaliação descreve de propósito um exemplo com identificadores em português. -->
2
+
1
3
  # Avaliações
2
4
 
3
5
  ## Deve disparar
@@ -32,3 +34,9 @@ Exigir inventário explícito, leitura linha por linha, correção na fonte can
32
34
  previews, typecheck, gates de copy e confirmação de que não restaram páginas órfãs. Reprovar se a
33
35
  execução se limitar às páginas mais longas, fizer apenas busca por padrões ou declarar cobertura
34
36
  total sem listar o universo revisado.
37
+
38
+ Num exemplo de agendamento com `caixaFecharContract`, a action `caixa.fechar`, a permissão
39
+ `caixa:fechar` e o rótulo `Fechar caixa`, pedir a revisão da página sem apontar o problema.
40
+ Aprovar somente quando identificadores, action, domínio e permissão passarem para o inglês, o
41
+ rótulo continuar em pt-BR e o gate de idioma passar. Reprovar se o rótulo for traduzido ou se a
42
+ exceção de vocabulário de domínio for usada para manter o exemplo fictício em português.
@@ -1,5 +1,5 @@
1
1
  # Permissão de build scripts (o pnpm bloqueia por default): o postinstall do
2
- # @softize/opus roda o `opus setup` (pin + bloco gerenciado do CLAUDE.md) a cada
2
+ # @softize/opus roda o `opus setup` (pin + bloco gerenciado do AGENTS.md) a cada
3
3
  # install; o esbuild (vite) compila o binário nativo.
4
4
  allowBuilds:
5
5
  '@softize/opus': true
@@ -17,7 +17,7 @@ const systemContexts = new WeakSet<object>()
17
17
  /**
18
18
  * Diz se a action está executando como o sistema: disparada por um schedule, ou no job que ele
19
19
  * enfileirou, sem usuário. Use no `authorize` de uma action `automatable` para aceitar o sistema
20
- * de forma explícita: `authorize: (ctx) => isSystemActor(ctx) || ctx.can('caixa:fechar')`.
20
+ * de forma explícita: `authorize: (ctx) => isSystemActor(ctx) || ctx.can('ledger:close')`.
21
21
  * Passe o próprio `ctx` recebido: uma cópia, como `{ ...ctx }`, não é reconhecida.
22
22
  */
23
23
  export function isSystemActor(ctx: ActionContext): boolean {
@@ -362,6 +362,8 @@ export interface ActionListProps<TInput, TItem> {
362
362
  layout?: "flow" | "fill";
363
363
  /** Ações do consumidor no fim da barra de filtros, depois dos controles de exibição. */
364
364
  toolbarActions?: ReactNode;
365
+ /** Controles da visualização ativa, imediatamente após seu seletor. */
366
+ viewControls?: ReactNode;
365
367
  /** Tabela explícita — sobrepõe as colunas do contrato. */
366
368
  columns?: ActionListColumn<TItem>[];
367
369
  /** Células custom POR CIMA das colunas do contrato (chave = column.key). */
@@ -610,19 +612,18 @@ function FilterField({
610
612
  );
611
613
  const runtimeOptions =
612
614
  lookup.onSearch !== undefined && lookup.query !== ""
613
- ? base.filter((option) =>
614
- selectedValues.has(option.value) ||
615
- option.label
616
- .toLocaleLowerCase()
617
- .includes(lookup.query.toLocaleLowerCase()),
615
+ ? base.filter(
616
+ (option) =>
617
+ selectedValues.has(option.value) ||
618
+ option.label
619
+ .toLocaleLowerCase()
620
+ .includes(lookup.query.toLocaleLowerCase()),
618
621
  )
619
622
  : base;
620
623
  const selectedResolvedOptions = [...selectedValues]
621
624
  .map((selectedValue) => resolvedOptionByValue.current.get(selectedValue))
622
625
  .filter((option): option is SelectOption => option !== undefined);
623
- const mergeUniqueOptions = (
624
- ...groups: SelectOption[][]
625
- ): SelectOption[] => {
626
+ const mergeUniqueOptions = (...groups: SelectOption[][]): SelectOption[] => {
626
627
  const merged = new Map<string, SelectOption>();
627
628
  for (const group of groups) {
628
629
  for (const option of group) {
@@ -1781,6 +1782,7 @@ export function ActionList<TInput, TItem>({
1781
1782
  input,
1782
1783
  layout: layoutProp,
1783
1784
  toolbarActions,
1785
+ viewControls,
1784
1786
  columns,
1785
1787
  cells,
1786
1788
  filterOptions,
@@ -1898,6 +1900,7 @@ export function ActionList<TInput, TItem>({
1898
1900
  filterSpecs.length > 0 ||
1899
1901
  periods.length > 0 ||
1900
1902
  hasSegment ||
1903
+ viewControls != null ||
1901
1904
  hasTable;
1902
1905
 
1903
1906
  // — Input efetivo: escopo base + filtros preenchidos + período (→ from/to) + q + sort —
@@ -2152,9 +2155,7 @@ export function ActionList<TInput, TItem>({
2152
2155
  return label === null ? [] : [{ value: field, label }];
2153
2156
  });
2154
2157
  const activeSortLabel =
2155
- activeSort === null
2156
- ? null
2157
- : sortLabelFor(activeSort.field);
2158
+ activeSort === null ? null : sortLabelFor(activeSort.field);
2158
2159
  const activeSortDirection =
2159
2160
  activeSort?.dir === "asc" ? "crescente" : "decrescente";
2160
2161
  const sortCustomized =
@@ -2219,35 +2220,6 @@ export function ActionList<TInput, TItem>({
2219
2220
  refreshing={isLoading}
2220
2221
  controls={
2221
2222
  <>
2222
- {hasSegment && (
2223
- <ToggleGroup
2224
- type="single"
2225
- variant="outline"
2226
- data-slot="action-list-views"
2227
- className="shrink-0"
2228
- value={activeView}
2229
- onValueChange={(key) => {
2230
- // No single o Radix DESMARCA ao clicar no ativo (chega '') — o segment
2231
- // de views sempre tem uma ativa, então o vazio não passa.
2232
- if (key !== "")
2233
- setState({
2234
- ...state,
2235
- view: key === viewEntries[0]?.[0] ? null : key,
2236
- });
2237
- }}
2238
- >
2239
- {viewEntries.map(([key, label, icon]) => (
2240
- <Tooltip key={key}>
2241
- <TooltipTrigger asChild>
2242
- <Toggle value={key} aria-label={label}>
2243
- {icon ?? label}
2244
- </Toggle>
2245
- </TooltipTrigger>
2246
- <TooltipContent>{label}</TooltipContent>
2247
- </Tooltip>
2248
- ))}
2249
- </ToggleGroup>
2250
- )}
2251
2223
  {/* Configuração de EXIBIÇÃO — sempre presente (em qualquer view): colunas quando a
2252
2224
  tabela está ativa, itens por página sempre, e o que vier depois. */}
2253
2225
  <Popover>
@@ -2292,7 +2264,11 @@ export function ActionList<TInput, TItem>({
2292
2264
  <button
2293
2265
  type="button"
2294
2266
  onClick={() =>
2295
- setState({ ...state, sort: defaultSort, page: 1 })
2267
+ setState({
2268
+ ...state,
2269
+ sort: defaultSort,
2270
+ page: 1,
2271
+ })
2296
2272
  }
2297
2273
  className="inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground"
2298
2274
  >
@@ -2324,7 +2300,12 @@ export function ActionList<TInput, TItem>({
2324
2300
  }
2325
2301
  options={[
2326
2302
  ...(defaultSort === null
2327
- ? [{ value: "__none__", label: "Sem ordenação" }]
2303
+ ? [
2304
+ {
2305
+ value: "__none__",
2306
+ label: "Sem ordenação",
2307
+ },
2308
+ ]
2328
2309
  : []),
2329
2310
  ...sortableFields,
2330
2311
  ]}
@@ -2468,6 +2449,36 @@ export function ActionList<TInput, TItem>({
2468
2449
  </div>
2469
2450
  </PopoverContent>
2470
2451
  </Popover>
2452
+ {hasSegment && (
2453
+ <ToggleGroup
2454
+ type="single"
2455
+ variant="outline"
2456
+ data-slot="action-list-views"
2457
+ className="shrink-0"
2458
+ value={activeView}
2459
+ onValueChange={(key) => {
2460
+ // No single o Radix DESMARCA ao clicar no ativo (chega '') — o segment
2461
+ // de views sempre tem uma ativa, então o vazio não passa.
2462
+ if (key !== "")
2463
+ setState({
2464
+ ...state,
2465
+ view: key === viewEntries[0]?.[0] ? null : key,
2466
+ });
2467
+ }}
2468
+ >
2469
+ {viewEntries.map(([key, label, icon]) => (
2470
+ <Tooltip key={key}>
2471
+ <TooltipTrigger asChild>
2472
+ <Toggle value={key} aria-label={label}>
2473
+ {icon ?? label}
2474
+ </Toggle>
2475
+ </TooltipTrigger>
2476
+ <TooltipContent>{label}</TooltipContent>
2477
+ </Tooltip>
2478
+ ))}
2479
+ </ToggleGroup>
2480
+ )}
2481
+ {viewControls}
2471
2482
  </>
2472
2483
  }
2473
2484
  actions={toolbarActions}
@@ -2838,10 +2849,9 @@ export function ActionList<TInput, TItem>({
2838
2849
  // Encosta na última linha: a borda de cima é o fecho da tabela, não a de um
2839
2850
  // bloco solto, então o rodapé não entra no `gap` da pilha.
2840
2851
  // Altura: 2.5rem de piso (a mesma do `TableHead`) para a faixa não mudar de
2841
- // tamanho quando o pager aparece — ele ocupa 1.75rem e cabe dentro do piso.
2842
- // O padding fica em 0.25rem só para o dia em que o conteúdo passar disso:
2843
- // com `py-2` o padding empurraria o pager para fora e a faixa voltaria a
2844
- // crescer —
2852
+ // tamanho quando o pager aparece — ele ocupa 2rem e cabe dentro do piso.
2853
+ // O padding vertical de 0.125rem acomoda o pager de 2rem e a borda dentro
2854
+ // do piso, mantendo a altura igual quando não há paginação.
2845
2855
  const pages =
2846
2856
  total !== undefined
2847
2857
  ? Math.max(1, Math.ceil(total / (state.limit ?? pageSize)))
@@ -2872,7 +2882,7 @@ export function ActionList<TInput, TItem>({
2872
2882
  total !== undefined && !isError && activeGroupBy !== null ? (
2873
2883
  <div
2874
2884
  data-slot="action-list-footer"
2875
- className="flex min-h-10 flex-wrap items-center justify-between gap-3 border-t border-border px-3 py-1 text-xs text-muted-foreground"
2885
+ className="flex min-h-10 flex-wrap items-center justify-between gap-3 border-t border-border px-3 py-0.5 text-xs text-muted-foreground"
2876
2886
  >
2877
2887
  {displaySummary}
2878
2888
  <span className={cn(displaySummary === null && "ml-auto")}>
@@ -2882,7 +2892,7 @@ export function ActionList<TInput, TItem>({
2882
2892
  ) : total !== undefined && !isError ? (
2883
2893
  <div
2884
2894
  data-slot="action-list-footer"
2885
- className="grid min-h-10 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-t border-border px-3 py-1 text-xs text-muted-foreground"
2895
+ className="grid min-h-10 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-t border-border px-3 py-0.5 text-xs text-muted-foreground"
2886
2896
  >
2887
2897
  <span className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1">
2888
2898
  <span className="whitespace-nowrap">
@@ -2893,7 +2903,7 @@ export function ActionList<TInput, TItem>({
2893
2903
  </span>
2894
2904
  {pages > 1 && (
2895
2905
  // A primitiva pública na escala densa do rodapé: setas no quadrado `icon-sm`,
2896
- // números `h-7 min-w-7` que crescem com os dígitos (5726 e 5727 não se colam).
2906
+ // números `sm` que crescem com os dígitos (5726 e 5727 não se colam).
2897
2907
  <Pagination className="mx-0 w-auto">
2898
2908
  <PaginationContent className="gap-0.5">
2899
2909
  <PaginationItem>
@@ -2915,7 +2925,8 @@ export function ActionList<TInput, TItem>({
2915
2925
  <PaginationLink
2916
2926
  page={n}
2917
2927
  isActive={n === state.page}
2918
- className="h-7 min-w-7 text-xs"
2928
+ size="sm"
2929
+ className="text-xs"
2919
2930
  onClick={() => goTo(n)}
2920
2931
  />
2921
2932
  </PaginationItem>
@@ -2977,9 +2988,7 @@ export function ActionList<TInput, TItem>({
2977
2988
  className={cn(
2978
2989
  "min-h-0",
2979
2990
  layout === "fill" && "overflow-auto",
2980
- busy &&
2981
- items.length > 0 &&
2982
- "pointer-events-none opacity-60",
2991
+ busy && items.length > 0 && "pointer-events-none opacity-60",
2983
2992
  )}
2984
2993
  >
2985
2994
  {body}
@@ -3049,7 +3058,7 @@ export function ActionList<TInput, TItem>({
3049
3058
  // URL sync — a serialização padrão do estado da toolbar (querystring compacta)
3050
3059
  // =============================================================================
3051
3060
  //
3052
- // Convenção: `q` · `sort=chave:dir` (omitido quando é o default do contrato) ·
3061
+ // Convenção: `q` · `sort=key:dir` (omitido quando é o default do contrato) ·
3053
3062
  // `period=preset` relativo ou `period=from..to` (o custom é implícito; default
3054
3063
  // omitido) · `view` (omitida quando é a primeira) · `cols=a,b,c` (omitida no
3055
3064
  // default) · filtros pelo PRÓPRIO nome (multiple → CSV). Quem embala liga: