@softize/opus 8.6.6
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 +1616 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/cli.mjs +528 -0
- package/bin/lib/check.mjs +307 -0
- package/bin/lib/components.mjs +151 -0
- package/bin/lib/create.mjs +208 -0
- package/bin/lib/db-check-runner.mjs +86 -0
- package/bin/lib/db-migrate-runner.mjs +89 -0
- package/bin/lib/db-scaffold-runner.mjs +84 -0
- package/bin/lib/db.mjs +261 -0
- package/bin/lib/docs-include.mjs +48 -0
- package/bin/lib/gen-dicts.mjs +134 -0
- package/bin/lib/gen-docs.mjs +288 -0
- package/bin/lib/gen-manifest.mjs +102 -0
- package/bin/lib/gen-openapi.mjs +195 -0
- package/bin/lib/gen-runner.mjs +472 -0
- package/bin/lib/gen-stubs.mjs +463 -0
- package/bin/lib/gen.mjs +311 -0
- package/bin/lib/init.mjs +514 -0
- package/bin/lib/introspect.mjs +107 -0
- package/bin/lib/mcp.mjs +85 -0
- package/bin/lib/postinstall.mjs +56 -0
- package/docs/chat-event-protocol.md +85 -0
- package/docs/code-style.md +16 -0
- package/docs/data-layer.md +246 -0
- package/docs/ownership-vs-shadcn-lock.md +102 -0
- package/docs/protocol.md +2053 -0
- package/docs/releasing.md +110 -0
- package/docs/shellnav.md +131 -0
- package/package.json +338 -0
- package/registry/hooks/hooks.json +26 -0
- package/registry/hooks/link-memory-on-start.mjs +46 -0
- package/registry/hooks/opus-check-on-stop.mjs +114 -0
- package/registry/skills/create-action/SKILL.md +49 -0
- package/registry/skills/create-action/scaffold.mjs +122 -0
- package/registry/templates/app/_gitignore +3 -0
- package/registry/templates/app/_npmrc +1 -0
- package/registry/templates/app/_opus/_gitignore +5 -0
- package/registry/templates/app/_prettierrc.json +6 -0
- package/registry/templates/app/index.html +13 -0
- package/registry/templates/app/opus.config.ts +16 -0
- package/registry/templates/app/package.json +43 -0
- package/registry/templates/app/pnpm-workspace.yaml +11 -0
- package/registry/templates/app/public/favicon.svg +4 -0
- package/registry/templates/app/src/App.tsx +37 -0
- package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
- package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
- package/registry/templates/app/src/domains/tasks/index.ts +13 -0
- package/registry/templates/app/src/index.css +18 -0
- package/registry/templates/app/src/main.tsx +25 -0
- package/registry/templates/app/tsconfig.json +20 -0
- package/registry/templates/app/vite.config.ts +46 -0
- package/registry/templates/monorepo/_gitignore +3 -0
- package/registry/templates/monorepo/_npmrc +1 -0
- package/registry/templates/monorepo/package.json +9 -0
- package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
- package/src/ai/ask.ts +64 -0
- package/src/ai/drivers/anthropic.ts +309 -0
- package/src/ai/index.ts +17 -0
- package/src/audit/drivers/console.ts +117 -0
- package/src/audit/drivers/pg.ts +172 -0
- package/src/audit/index.ts +51 -0
- package/src/auth/drivers/better-auth.ts +103 -0
- package/src/auth/drivers/jwt.ts +188 -0
- package/src/auth/index.ts +9 -0
- package/src/client/drivers/fetch.ts +202 -0
- package/src/client/index.ts +22 -0
- package/src/core/actions.ts +110 -0
- package/src/core/audit.ts +239 -0
- package/src/core/contracts.ts +137 -0
- package/src/core/domain.ts +310 -0
- package/src/core/errors.ts +181 -0
- package/src/core/index.ts +174 -0
- package/src/core/logical-type.ts +31 -0
- package/src/core/reactions.ts +81 -0
- package/src/core/runtime.ts +1167 -0
- package/src/core/schedules.ts +41 -0
- package/src/core/types.ts +1356 -0
- package/src/data/drivers/kysely.ts +389 -0
- package/src/data/index.ts +10 -0
- package/src/data/readonly-pool.ts +160 -0
- package/src/dsl/eval.ts +136 -0
- package/src/dsl/index.ts +29 -0
- package/src/dsl/kysely.ts +230 -0
- package/src/dsl/loads.ts +123 -0
- package/src/dsl/parser.ts +423 -0
- package/src/dsl/types.ts +113 -0
- package/src/events/drivers/mitt.ts +70 -0
- package/src/events/index.ts +9 -0
- package/src/log/drivers/pino.ts +57 -0
- package/src/log/index.ts +9 -0
- package/src/mcp/index.ts +62 -0
- package/src/queue/drivers/bullmq.ts +190 -0
- package/src/queue/index.ts +9 -0
- package/src/scheduler/drivers/node-cron.ts +93 -0
- package/src/scheduler/every.ts +45 -0
- package/src/scheduler/index.ts +9 -0
- package/src/schema/drivers/zod.ts +765 -0
- package/src/schema/entity.ts +439 -0
- package/src/schema/format/locale.ts +144 -0
- package/src/schema/index.ts +65 -0
- package/src/schema/openapi.ts +302 -0
- package/src/schema/scaffold.ts +160 -0
- package/src/server/drivers/fastify.ts +224 -0
- package/src/server/drivers/node.ts +386 -0
- package/src/server/index.ts +142 -0
- package/src/storage/drivers/fs.ts +90 -0
- package/src/storage/drivers/s3.ts +117 -0
- package/src/storage/index.ts +27 -0
- package/src/testing/fake.ts +298 -0
- package/src/testing/index.ts +324 -0
- package/src/ui/components/patterns/action-form-card.tsx +48 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
- package/src/ui/components/patterns/app-shell.tsx +227 -0
- package/src/ui/components/patterns/confirm.tsx +226 -0
- package/src/ui/components/patterns/data-state.tsx +75 -0
- package/src/ui/components/patterns/form-dialog.tsx +64 -0
- package/src/ui/components/patterns/form.tsx +584 -0
- package/src/ui/components/patterns/list.tsx +1488 -0
- package/src/ui/components/patterns/page.tsx +46 -0
- package/src/ui/components/patterns/section-shell.tsx +246 -0
- package/src/ui/components/patterns/shell-nav.tsx +150 -0
- package/src/ui/components/patterns/sidebar.tsx +89 -0
- package/src/ui/components/patterns/split.tsx +93 -0
- package/src/ui/components/patterns/trigger.tsx +196 -0
- package/src/ui/components/patterns/view.tsx +84 -0
- package/src/ui/components/primitives/accordion.tsx +64 -0
- package/src/ui/components/primitives/alert-dialog.tsx +190 -0
- package/src/ui/components/primitives/alert.tsx +116 -0
- package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
- package/src/ui/components/primitives/avatar.tsx +107 -0
- package/src/ui/components/primitives/badge.tsx +37 -0
- package/src/ui/components/primitives/breadcrumb.tsx +109 -0
- package/src/ui/components/primitives/button-group.tsx +83 -0
- package/src/ui/components/primitives/button.tsx +102 -0
- package/src/ui/components/primitives/calendar.tsx +218 -0
- package/src/ui/components/primitives/card.tsx +56 -0
- package/src/ui/components/primitives/carousel.tsx +239 -0
- package/src/ui/components/primitives/chat.tsx +407 -0
- package/src/ui/components/primitives/checkbox.tsx +30 -0
- package/src/ui/components/primitives/collapsible.tsx +31 -0
- package/src/ui/components/primitives/command.tsx +182 -0
- package/src/ui/components/primitives/composer.tsx +121 -0
- package/src/ui/components/primitives/copyable.tsx +50 -0
- package/src/ui/components/primitives/dialog.tsx +147 -0
- package/src/ui/components/primitives/drawer.tsx +141 -0
- package/src/ui/components/primitives/empty.tsx +104 -0
- package/src/ui/components/primitives/field.tsx +246 -0
- package/src/ui/components/primitives/icon-picker.tsx +180 -0
- package/src/ui/components/primitives/input-group.tsx +168 -0
- package/src/ui/components/primitives/input-otp.tsx +75 -0
- package/src/ui/components/primitives/input.tsx +72 -0
- package/src/ui/components/primitives/item.tsx +193 -0
- package/src/ui/components/primitives/kbd.tsx +28 -0
- package/src/ui/components/primitives/label.tsx +22 -0
- package/src/ui/components/primitives/markdown.tsx +35 -0
- package/src/ui/components/primitives/menu.tsx +255 -0
- package/src/ui/components/primitives/pagination.tsx +127 -0
- package/src/ui/components/primitives/popover.tsx +87 -0
- package/src/ui/components/primitives/progress.tsx +29 -0
- package/src/ui/components/primitives/radio-group.tsx +43 -0
- package/src/ui/components/primitives/resizable.tsx +51 -0
- package/src/ui/components/primitives/scroll-area.tsx +56 -0
- package/src/ui/components/primitives/select.tsx +479 -0
- package/src/ui/components/primitives/separator.tsx +26 -0
- package/src/ui/components/primitives/skeleton.tsx +13 -0
- package/src/ui/components/primitives/slider.tsx +61 -0
- package/src/ui/components/primitives/sonner.tsx +46 -0
- package/src/ui/components/primitives/spinner.tsx +29 -0
- package/src/ui/components/primitives/switch.tsx +33 -0
- package/src/ui/components/primitives/table.tsx +114 -0
- package/src/ui/components/primitives/tabs.tsx +104 -0
- package/src/ui/components/primitives/textarea.tsx +18 -0
- package/src/ui/components/primitives/toggle-group.tsx +81 -0
- package/src/ui/components/primitives/toggle.tsx +45 -0
- package/src/ui/components/primitives/tooltip.tsx +55 -0
- package/src/ui/components/primitives/truncate.tsx +49 -0
- package/src/ui/docs/DocBrowser.tsx +90 -0
- package/src/ui/docs/changelog.tsx +80 -0
- package/src/ui/docs/content/accordion.md +86 -0
- package/src/ui/docs/content/action-form-card.md +24 -0
- package/src/ui/docs/content/action-form-dialog.md +30 -0
- package/src/ui/docs/content/action-form.md +125 -0
- package/src/ui/docs/content/action-list-dialog.md +68 -0
- package/src/ui/docs/content/action-list.md +194 -0
- package/src/ui/docs/content/action-trigger.md +72 -0
- package/src/ui/docs/content/action-view.md +47 -0
- package/src/ui/docs/content/actions.md +138 -0
- package/src/ui/docs/content/ai.md +112 -0
- package/src/ui/docs/content/alert-dialog.md +73 -0
- package/src/ui/docs/content/alert.md +69 -0
- package/src/ui/docs/content/app-shell.md +155 -0
- package/src/ui/docs/content/aspect-ratio.md +66 -0
- package/src/ui/docs/content/audit.md +84 -0
- package/src/ui/docs/content/auth.md +70 -0
- package/src/ui/docs/content/avatar.md +94 -0
- package/src/ui/docs/content/badge.md +48 -0
- package/src/ui/docs/content/breadcrumb.md +87 -0
- package/src/ui/docs/content/button-group.md +71 -0
- package/src/ui/docs/content/button.md +60 -0
- package/src/ui/docs/content/calendar.md +62 -0
- package/src/ui/docs/content/card.md +49 -0
- package/src/ui/docs/content/carousel.md +85 -0
- package/src/ui/docs/content/chat.md +69 -0
- package/src/ui/docs/content/checkbox.md +75 -0
- package/src/ui/docs/content/cli.md +58 -0
- package/src/ui/docs/content/collapsible.md +64 -0
- package/src/ui/docs/content/command.md +56 -0
- package/src/ui/docs/content/composer.md +50 -0
- package/src/ui/docs/content/confirm.md +120 -0
- package/src/ui/docs/content/copyable.md +30 -0
- package/src/ui/docs/content/customization.md +110 -0
- package/src/ui/docs/content/cycle.md +34 -0
- package/src/ui/docs/content/data-state.md +47 -0
- package/src/ui/docs/content/data.md +99 -0
- package/src/ui/docs/content/dialog.md +60 -0
- package/src/ui/docs/content/drawer.md +55 -0
- package/src/ui/docs/content/empty.md +66 -0
- package/src/ui/docs/content/events.md +61 -0
- package/src/ui/docs/content/field.md +58 -0
- package/src/ui/docs/content/getting-started.md +109 -0
- package/src/ui/docs/content/icon-picker.md +51 -0
- package/src/ui/docs/content/input-group.md +78 -0
- package/src/ui/docs/content/input-otp.md +72 -0
- package/src/ui/docs/content/input.md +78 -0
- package/src/ui/docs/content/item.md +84 -0
- package/src/ui/docs/content/kbd.md +62 -0
- package/src/ui/docs/content/label.md +32 -0
- package/src/ui/docs/content/log.md +55 -0
- package/src/ui/docs/content/markdown.md +41 -0
- package/src/ui/docs/content/mcp.md +44 -0
- package/src/ui/docs/content/menu.md +114 -0
- package/src/ui/docs/content/microcopy.md +83 -0
- package/src/ui/docs/content/page.md +34 -0
- package/src/ui/docs/content/pagination.md +99 -0
- package/src/ui/docs/content/popover.md +49 -0
- package/src/ui/docs/content/progress.md +69 -0
- package/src/ui/docs/content/queue.md +62 -0
- package/src/ui/docs/content/radio-group.md +77 -0
- package/src/ui/docs/content/resizable.md +86 -0
- package/src/ui/docs/content/router.md +56 -0
- package/src/ui/docs/content/runtime.md +77 -0
- package/src/ui/docs/content/scheduler.md +66 -0
- package/src/ui/docs/content/scroll-area.md +89 -0
- package/src/ui/docs/content/section-shell.md +121 -0
- package/src/ui/docs/content/select.md +342 -0
- package/src/ui/docs/content/separator.md +33 -0
- package/src/ui/docs/content/sidebar.md +38 -0
- package/src/ui/docs/content/skeleton.md +34 -0
- package/src/ui/docs/content/slider.md +64 -0
- package/src/ui/docs/content/spinner.md +37 -0
- package/src/ui/docs/content/split.md +33 -0
- package/src/ui/docs/content/storage.md +69 -0
- package/src/ui/docs/content/switch.md +69 -0
- package/src/ui/docs/content/table.md +102 -0
- package/src/ui/docs/content/tabs.md +94 -0
- package/src/ui/docs/content/testing.md +89 -0
- package/src/ui/docs/content/textarea.md +30 -0
- package/src/ui/docs/content/toast.md +67 -0
- package/src/ui/docs/content/toggle-group.md +81 -0
- package/src/ui/docs/content/toggle.md +72 -0
- package/src/ui/docs/content/tokens.md +171 -0
- package/src/ui/docs/content/tooltip.md +50 -0
- package/src/ui/docs/content/truncate.md +37 -0
- package/src/ui/docs/content/ui.md +40 -0
- package/src/ui/docs/content/upgrading.md +48 -0
- package/src/ui/docs/doc-client.tsx +214 -0
- package/src/ui/docs/doc.tsx +301 -0
- package/src/ui/docs/folder.tsx +149 -0
- package/src/ui/docs/index.ts +21 -0
- package/src/ui/docs/markdown.tsx +130 -0
- package/src/ui/docs/md-raw.d.ts +4 -0
- package/src/ui/docs/plugin.ts +104 -0
- package/src/ui/docs/registry.tsx +424 -0
- package/src/ui/docs/standalone.tsx +107 -0
- package/src/ui/drivers/react.tsx +627 -0
- package/src/ui/index.ts +92 -0
- package/src/ui/lib/cn.ts +10 -0
- package/src/ui/lib/zod-pt-br.ts +38 -0
- package/src/ui/meta.ts +412 -0
- package/src/ui/react.tsx +235 -0
- package/src/ui/router.ts +96 -0
- package/src/ui/theme.css +234 -0
- package/src/vite/design.ts +652 -0
- package/src/vite/index.ts +8 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
## Padrão (single)
|
|
2
|
+
|
|
3
|
+
`type=single` abre um painel por vez. `collapsible` deixa fechar o que está aberto — sem ele,
|
|
4
|
+
sempre fica um item expandido. `defaultValue` deixa o estado com o componente.
|
|
5
|
+
|
|
6
|
+
```tsx preview
|
|
7
|
+
<Accordion type="single" collapsible defaultValue="overview" className="w-full">
|
|
8
|
+
<AccordionItem value="overview">
|
|
9
|
+
<AccordionTrigger>Visão geral</AccordionTrigger>
|
|
10
|
+
<AccordionContent>
|
|
11
|
+
<p className="text-muted-foreground">Resumo do workspace Empresa X.</p>
|
|
12
|
+
</AccordionContent>
|
|
13
|
+
</AccordionItem>
|
|
14
|
+
<AccordionItem value="agents">
|
|
15
|
+
<AccordionTrigger>Agentes</AccordionTrigger>
|
|
16
|
+
<AccordionContent>
|
|
17
|
+
<p className="text-muted-foreground">developer, reviewer e designer ativos.</p>
|
|
18
|
+
</AccordionContent>
|
|
19
|
+
</AccordionItem>
|
|
20
|
+
<AccordionItem value="repos">
|
|
21
|
+
<AccordionTrigger>Repositórios</AccordionTrigger>
|
|
22
|
+
<AccordionContent>
|
|
23
|
+
<p className="text-muted-foreground">empresa-x-api e empresa-x-web vinculados.</p>
|
|
24
|
+
</AccordionContent>
|
|
25
|
+
</AccordionItem>
|
|
26
|
+
</Accordion>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Múltiplos abertos
|
|
30
|
+
|
|
31
|
+
`type=multiple` permite vários painéis expandidos ao mesmo tempo — `defaultValue` vira um
|
|
32
|
+
array com os itens abertos de saída.
|
|
33
|
+
|
|
34
|
+
```tsx preview
|
|
35
|
+
<Accordion type="multiple" defaultValue={['skills', 'prompt']} className="w-full">
|
|
36
|
+
<AccordionItem value="prompt">
|
|
37
|
+
<AccordionTrigger>Prompt</AccordionTrigger>
|
|
38
|
+
<AccordionContent>
|
|
39
|
+
<p className="text-muted-foreground">O prompt do agente developer.</p>
|
|
40
|
+
</AccordionContent>
|
|
41
|
+
</AccordionItem>
|
|
42
|
+
<AccordionItem value="skills">
|
|
43
|
+
<AccordionTrigger>Skills</AccordionTrigger>
|
|
44
|
+
<AccordionContent>
|
|
45
|
+
<p className="text-muted-foreground">clean-code, code-style, ddd e test vinculadas.</p>
|
|
46
|
+
</AccordionContent>
|
|
47
|
+
</AccordionItem>
|
|
48
|
+
</Accordion>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Controlado (com estado)
|
|
52
|
+
|
|
53
|
+
No modo `single`, `value`/`onValueChange` tiram o estado do componente — dá pra abrir um item
|
|
54
|
+
de fora. Exemplo **com estado** (o `render()` deixa o hook rodar):
|
|
55
|
+
|
|
56
|
+
```tsx preview
|
|
57
|
+
const [open, setOpen] = React.useState('developer')
|
|
58
|
+
|
|
59
|
+
render(
|
|
60
|
+
<Accordion type="single" collapsible value={open} onValueChange={setOpen} className="w-full">
|
|
61
|
+
<AccordionItem value="developer">
|
|
62
|
+
<AccordionTrigger>Agente developer</AccordionTrigger>
|
|
63
|
+
<AccordionContent>
|
|
64
|
+
<p className="text-muted-foreground">Implementa a task no worktree da sessão.</p>
|
|
65
|
+
</AccordionContent>
|
|
66
|
+
</AccordionItem>
|
|
67
|
+
<AccordionItem value="reviewer">
|
|
68
|
+
<AccordionTrigger>Agente reviewer</AccordionTrigger>
|
|
69
|
+
<AccordionContent>
|
|
70
|
+
<p className="text-muted-foreground">Revisa o handoff e carimba o diff.</p>
|
|
71
|
+
</AccordionContent>
|
|
72
|
+
</AccordionItem>
|
|
73
|
+
</Accordion>,
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Props
|
|
78
|
+
|
|
79
|
+
| Prop | Tipo | Default | Descrição |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| `type` (Accordion) | `'single' \| 'multiple'` | | single abre um painel por vez; multiple permite vários. |
|
|
82
|
+
| `collapsible` (Accordion) | `boolean` | `false` | Só no single — permite fechar o item aberto. |
|
|
83
|
+
| `defaultValue` (Accordion) | `string \| string[]` | | Item(ns) aberto(s) no modo não controlado. |
|
|
84
|
+
| `value` (Accordion) | `string \| string[]` | | Item(ns) aberto(s) no modo controlado — pareie com onValueChange. |
|
|
85
|
+
| `onValueChange` (Accordion) | `(value) => void` | | Chamado quando o usuário abre ou fecha um item. |
|
|
86
|
+
| `value` (AccordionItem) | `string` | | Identificador do item — é o que defaultValue/value referenciam. |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## Form como card
|
|
2
|
+
|
|
3
|
+
O ActionForm dentro de um Card — header, conteúdo e rodapé, no respiro do Card (sem divisor nem
|
|
4
|
+
faixa de modal). Pra estruturar uma seção da página como painel. O título é opcional.
|
|
5
|
+
|
|
6
|
+
```tsx preview col md
|
|
7
|
+
<DocBrowserActionProvider>
|
|
8
|
+
<ActionFormCard
|
|
9
|
+
title="Editar workspace"
|
|
10
|
+
action={docWorkspaceCreate}
|
|
11
|
+
defaultValues={{ name: 'Empresa X', status: 'active' }}
|
|
12
|
+
submitLabel="Salvar"
|
|
13
|
+
/>
|
|
14
|
+
</DocBrowserActionProvider>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Props
|
|
18
|
+
|
|
19
|
+
| Prop | Tipo | Default | Descrição |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| `title` | `string` | | Título do header (com divisor embaixo). Sem ele, o card começa direto no corpo. |
|
|
22
|
+
| `description` | `string` | | Subtítulo opcional, abaixo do título. |
|
|
23
|
+
| `action / defaultValues / fieldOptions / submitLabel / onSuccess / onCancel` | `— (iguais ao ActionForm)` | | O resto é o ActionForm — o card injeta o conteúdo (CardContent) e o rodapé (CardFooter) por baixo. |
|
|
24
|
+
| `cardClassName` | `string` | | Classes da SUPERFÍCIE do card (ex.: largura). `className` vai pro `<form>`. |
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Form em modal
|
|
2
|
+
|
|
3
|
+
O open é de quem orquestra; o fechamento no sucesso é do pattern. Header e X fixos, os campos
|
|
4
|
+
scrollam, o rodapé é a faixa da casa. O preview roda num client de mentira — no app, o contrato
|
|
5
|
+
vem do spec.
|
|
6
|
+
|
|
7
|
+
```tsx preview
|
|
8
|
+
const [open, setOpen] = useState(false)
|
|
9
|
+
|
|
10
|
+
render(
|
|
11
|
+
<DocBrowserActionProvider>
|
|
12
|
+
<Button onClick={() => setOpen(true)}><Plus /> Novo workspace</Button>
|
|
13
|
+
<ActionFormDialog
|
|
14
|
+
open={open}
|
|
15
|
+
onOpenChange={setOpen}
|
|
16
|
+
title="Novo workspace"
|
|
17
|
+
description="O workspace agrupa os repositórios e agentes do cliente."
|
|
18
|
+
action={docWorkspaceCreate}
|
|
19
|
+
/>
|
|
20
|
+
</DocBrowserActionProvider>,
|
|
21
|
+
)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Props
|
|
25
|
+
|
|
26
|
+
| Prop | Tipo | Default | Descrição |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `open / onOpenChange` | `boolean / (open: boolean) => void` | | Controle do modal — o pattern chama onOpenChange(false) no sucesso e no Cancelar. |
|
|
29
|
+
| `title / description` | `string` | | O DialogHeader fixo — description é opcional. |
|
|
30
|
+
| `…ActionFormProps` | `action, defaultValues, onSuccess, fieldOptions…` | | Todo o resto desce pro ActionForm interno — mesma API da página dele. |
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
## Form derivado do contrato
|
|
2
|
+
|
|
3
|
+
Zero JSX de campo: enum vira Select, string longa vira Textarea, obrigatório ganha o asterisco. O
|
|
4
|
+
preview roda num client de mentira — digite "Softize" no nome pra ver o erro de servidor inline; o
|
|
5
|
+
submit só habilita com mudança real (dirty).
|
|
6
|
+
|
|
7
|
+
```tsx preview col md
|
|
8
|
+
<DocBrowserActionProvider>
|
|
9
|
+
<ActionForm action={docWorkspaceCreate} />
|
|
10
|
+
</DocBrowserActionProvider>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Composição (diagramação livre)
|
|
14
|
+
|
|
15
|
+
Com children, o AUTO desliga e você diagrama: `<ActionFormField name />` coloca cada campo — com
|
|
16
|
+
label, hint, widget, erro e asterisco derivados do contrato — onde quiser. Condicional é JSX;
|
|
17
|
+
opções de runtime entram por prop no campo. O comportamento (submit, validação, toast,
|
|
18
|
+
invalidação) continua encapsulado — e o espaçamento é 100% seu: o campo não impõe margem
|
|
19
|
+
(quem diagrama usa grid/gap/space-y como quiser).
|
|
20
|
+
|
|
21
|
+
```tsx preview col md
|
|
22
|
+
<DocBrowserActionProvider>
|
|
23
|
+
<ActionForm action={docWorkspaceCreate}>
|
|
24
|
+
<div className="space-y-6">
|
|
25
|
+
<div className="grid grid-cols-2 gap-4">
|
|
26
|
+
<ActionFormField name="name" />
|
|
27
|
+
<ActionFormField name="status" />
|
|
28
|
+
</div>
|
|
29
|
+
<ActionFormField name="contact" />
|
|
30
|
+
<ActionFormField name="notes" />
|
|
31
|
+
</div>
|
|
32
|
+
</ActionForm>
|
|
33
|
+
</DocBrowserActionProvider>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Valores iniciais e rótulos
|
|
37
|
+
|
|
38
|
+
defaultValues pré-carrega (modo edição); submitLabel/cancelLabel trocam o rodapé. O onCancel é de
|
|
39
|
+
quem orquestra (fechar drawer, voltar…).
|
|
40
|
+
|
|
41
|
+
```tsx preview col md
|
|
42
|
+
<DocBrowserActionProvider>
|
|
43
|
+
<ActionForm
|
|
44
|
+
action={docWorkspaceCreate}
|
|
45
|
+
defaultValues={{ name: 'Empresa X', status: 'active' }}
|
|
46
|
+
submitLabel="Atualizar"
|
|
47
|
+
onCancel={() => undefined}
|
|
48
|
+
/>
|
|
49
|
+
</DocBrowserActionProvider>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Pré-requisitos
|
|
53
|
+
|
|
54
|
+
O driver precisa dos dois providers no root — exatamente o wiring do admin (o consumidor
|
|
55
|
+
canônico).
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
/* main.tsx do app. */
|
|
59
|
+
<QueryClientProvider client={queryClient}>
|
|
60
|
+
<TbdlibProvider client={apiClient}>
|
|
61
|
+
<App />
|
|
62
|
+
</TbdlibProvider>
|
|
63
|
+
</QueryClientProvider>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Props
|
|
67
|
+
|
|
68
|
+
| Prop | Tipo | Default | Descrição |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| `action` | `FormContract<TInput, TData>` | | O contrato da FormAction — dele saem campos (input Zod + fields), mensagens e invalidação de cache. |
|
|
71
|
+
| `defaultValues` | `Partial<TInput>` | | Valores iniciais — o modo edição de um update/patch. |
|
|
72
|
+
| `onSuccess` | `(data: TData) => void` | | Pós-sucesso (o toast e a invalidação de cache já aconteceram). |
|
|
73
|
+
| `submitLabel / cancelLabel / onCancel` | `string / string / () => void` | `'Salvar' / 'Cancelar'` | Rodapé do form — o Cancelar só aparece com onCancel. |
|
|
74
|
+
| `fieldOptions` | `Record<string, SelectOption[]>` | | Opções de runtime por campo (ex.: ids de skills) — sobrepõe as inferidas do z.enum. |
|
|
75
|
+
| `className / body / footer` | `string / (node) => node / (node) => node` | | className = classes do `<form>`. body/footer = SLOTS: recebem os campos / os botões e escolhem o invólucro — é como o ActionFormDialog injeta DialogBody/DialogFooter (scroll + faixa). |
|
|
76
|
+
| `children` | `ReactNode` | | Modo COMPOSIÇÃO: diagrame com `<ActionFormField name />`. Sem children, o AUTO monta todos os campos na ordem do contrato. |
|
|
77
|
+
|
|
78
|
+
## ActionFormField
|
|
79
|
+
|
|
80
|
+
| Prop | Tipo | Descrição |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `name` | `string` | O campo do contrato (chave em `fields`/schema). Fora do schema, não renderiza. |
|
|
83
|
+
| `options` | `SelectOption[]` | Opções por id de runtime — sobrepõe o fieldOptions do form e o z.enum. |
|
|
84
|
+
| `className` | `string` | Classes do invólucro (ex.: `col-span-2` numa grid). |
|
|
85
|
+
|
|
86
|
+
## De onde vêm as opções de um select
|
|
87
|
+
|
|
88
|
+
Precedência, da mais específica pra mais automática: **prop `options` do campo** >
|
|
89
|
+
**`fieldOptions` do form** > **`options` do FieldSpec no contrato** (`{ kind: 'dictionary',
|
|
90
|
+
ref }` resolve pelos dicts do `<TbdlibProvider dicts={{ ref: meuDict }}>` — o `DictType` do
|
|
91
|
+
`t.dict` encaixa direto; `{ kind: 'static', items }` renderiza como declarado) > **meta do
|
|
92
|
+
`t.dict` no schema** (zero-config: campo `meuDict.zod()` — ou multiselect com elemento dict —
|
|
93
|
+
resolve value→label pela meta que viaja no contrato, sem registry) > **chaves cruas do
|
|
94
|
+
z.enum**. Campo texto com opções declaradas (runtime ou spec) vira single-select por-id.
|
|
95
|
+
|
|
96
|
+
## Controle custom: useActionFormContext
|
|
97
|
+
|
|
98
|
+
Campo com UI própria (grade de permissões, canvas…) que nenhum widget cobre? No modo
|
|
99
|
+
composição, o hook dá acesso ao form do contrato — o campo custom participa do submit
|
|
100
|
+
sem abandonar o `<ActionForm>`:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { useActionFormContext } from '@softize/opus/ui/react'
|
|
104
|
+
|
|
105
|
+
function PermissionGrid() {
|
|
106
|
+
const { form } = useActionFormContext()
|
|
107
|
+
const perms = (form.watch('permissions') as string[] | undefined) ?? []
|
|
108
|
+
const toggle = (p: string) =>
|
|
109
|
+
form.setValue(
|
|
110
|
+
'permissions',
|
|
111
|
+
perms.includes(p) ? perms.filter((x) => x !== p) : [...perms, p],
|
|
112
|
+
{ shouldValidate: true, shouldDirty: true },
|
|
113
|
+
)
|
|
114
|
+
const error = form.formState.errors['permissions']
|
|
115
|
+
// …sua grade; zod-resolver, erro inline e submit continuam do contrato.
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
<ActionForm action={roleUpdate}>
|
|
119
|
+
<ActionFormField name="name" />
|
|
120
|
+
<PermissionGrid />
|
|
121
|
+
</ActionForm>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
O contexto também expõe `shape` (Zod por campo), `fields` (FieldSpec) e `fieldOptions`.
|
|
125
|
+
Fora de um `<ActionForm>`, o hook explode com mensagem clara — igual ao `ActionFormField`.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
## ListAction em modal
|
|
2
|
+
|
|
3
|
+
O modo modal da listagem, action-driven: a lista é query-backed (busca no mount, re-busca quando o input muda e refaz sozinha quando um form/trigger invalida a action), os estados derivam do fetch, e a nota da toolbar já vem com o "N no total". Children diagrama os itens — composição, como nos irmãos.
|
|
4
|
+
|
|
5
|
+
```tsx preview col
|
|
6
|
+
const [open, setOpen] = useState(false)
|
|
7
|
+
|
|
8
|
+
render(
|
|
9
|
+
<DocBrowserActionProvider>
|
|
10
|
+
<Button onClick={() => setOpen(true)}>Workspaces</Button>
|
|
11
|
+
<ActionListDialog
|
|
12
|
+
action={docWorkspaceList}
|
|
13
|
+
input={{}}
|
|
14
|
+
open={open}
|
|
15
|
+
onOpenChange={setOpen}
|
|
16
|
+
title="Workspaces"
|
|
17
|
+
description="Back-office"
|
|
18
|
+
actions={
|
|
19
|
+
<Button size="sm" variant="outline">
|
|
20
|
+
<Plus /> Workspace
|
|
21
|
+
</Button>
|
|
22
|
+
}
|
|
23
|
+
emptyText="Nenhum workspace."
|
|
24
|
+
>
|
|
25
|
+
{(workspaces) => (
|
|
26
|
+
<div className="space-y-2">
|
|
27
|
+
{workspaces.map((w) => (
|
|
28
|
+
<div key={w.id} className="flex items-center gap-2 rounded-md border border-border p-3">
|
|
29
|
+
<span className="text-sm font-semibold">{w.name}</span>
|
|
30
|
+
<Badge variant={w.status === 'active' ? 'success' : 'warning'}>{w.status}</Badge>
|
|
31
|
+
<span className="ml-auto text-xs text-muted-foreground">{w.agents} agentes</span>
|
|
32
|
+
</div>
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
</ActionListDialog>
|
|
37
|
+
</DocBrowserActionProvider>,
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## O que o contrato não sabe
|
|
42
|
+
|
|
43
|
+
Dois escape hatches, pros casos em que o modal agrega mais de uma fonte: `loading` soma a carga de uma query irmã (ex.: os papéis que os cards precisam pra rotular) e `empty` sobrepõe o vazio derivado (ex.: com o form inline de criar aberto, a lista vazia mostra o form, não o emptyText).
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<ActionListDialog
|
|
47
|
+
action={agentListContract}
|
|
48
|
+
input={{ workspaceId }}
|
|
49
|
+
loading={rolesLoading} // A query irmã.
|
|
50
|
+
empty={(repos) => repos.length === 0 && !adding} // Form inline aberto ⇒ children.
|
|
51
|
+
...
|
|
52
|
+
/>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Props
|
|
56
|
+
|
|
57
|
+
| Prop | Tipo | Default | Descrição |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `action / input` | `ListAction / TInput` | | O contrato e os filtros — mudou o input, re-busca; `invalidates` de forms/triggers refaz sozinho. |
|
|
60
|
+
| `open / onOpenChange` | `boolean / (open) => void` | | Controle do modal — de quem orquestra. |
|
|
61
|
+
| `title / description` | `string` | | O DialogHeader fixo (em geral, o que é a lista e de quem). |
|
|
62
|
+
| `children` | `(items, refetch) => ReactNode` | | O layout dos itens (cards/linhas) — os estados já saíram daqui. |
|
|
63
|
+
| `note` | `ReactNode \| (items) => ReactNode` | `"N no total"` | Nota à esquerda da toolbar — o default é derivado dos itens. |
|
|
64
|
+
| `actions` | `ReactNode` | | Ação à direita da toolbar — em geral o botão de criar. |
|
|
65
|
+
| `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado. |
|
|
66
|
+
| `loading` | `boolean` | | Carga extra agregada à do fetch (query irmã). |
|
|
67
|
+
| `emptyText / errorText` | `string` | | Textos dos estados (DataState). |
|
|
68
|
+
| `className` | `string` | `sm:max-w-3xl` | Largura do DialogContent. |
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
## Declarativo pelo contrato (a diagramação da casa)
|
|
2
|
+
|
|
3
|
+
O contrato descreve, a UI deriva — zero configuração no call site. `columns` vira a tabela emoldurada (o datagrid da casa; tipos text/number/date/badge, `fit`, `hidden`, headers ordenáveis que escrevem `sort: 'chave:dir'` no input); `filters` vira a toolbar (os não-avançados inline, os `advanced: true` no modal "Filtros" com contador); `text` liga a busca (param `q`); filtros do modal aplicados viram chips removíveis. A linha é RESPONSIVA: a busca tem largura auto (encolhe primeiro) e filtro inline que não cabe migra pro modal — medição real, re-avaliada no resize; só no caso extremo (nada mais a ceder) a linha quebra. O handler implementa o que o input diz (orderBy, where) — mesmo modelo do resto do Opus.
|
|
4
|
+
|
|
5
|
+
```tsx preview col
|
|
6
|
+
render(
|
|
7
|
+
<DocBrowserActionProvider>
|
|
8
|
+
<div className="w-full">
|
|
9
|
+
<ActionList action={docWorkspaceList} input={{}} emptyMessage="Nenhum workspace." />
|
|
10
|
+
</div>
|
|
11
|
+
</DocBrowserActionProvider>,
|
|
12
|
+
)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Células custom (cells)
|
|
16
|
+
|
|
17
|
+
As colunas do contrato descrevem DADOS; apresentação especial entra por cima com `cells` (chave = key da coluna). É onde vivem chips coloridos por dict, links e a coluna de ações.
|
|
18
|
+
|
|
19
|
+
```tsx preview col
|
|
20
|
+
const STATUS_VARIANT = { active: 'success', onboarding: 'warning', paused: 'outline' }
|
|
21
|
+
|
|
22
|
+
render(
|
|
23
|
+
<DocBrowserActionProvider>
|
|
24
|
+
<div className="w-full">
|
|
25
|
+
<ActionList
|
|
26
|
+
action={docWorkspaceList}
|
|
27
|
+
input={{}}
|
|
28
|
+
cells={{
|
|
29
|
+
status: (w) => <Badge variant={STATUS_VARIANT[w.status]}>{w.status}</Badge>,
|
|
30
|
+
}}
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
</DocBrowserActionProvider>,
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Período (o campo dinâmico)
|
|
38
|
+
|
|
39
|
+
Com `periods` no contrato, a toolbar ganha o controle de período: um popover com os presets e, no "Personalizado", o calendário de range ali mesmo. Período é recorte OBRIGATÓRIO do caso de uso: o default (o preset com `default: true`, ou o primeiro) já vem aplicado e não existe "sem período". O preset fica RELATIVO na URL (`?period=last7`; o default é omitido); no custom o range vai direto no param (`?period=2026-07-01..2026-07-07` — o "custom" é implícito). No fetch, o pattern materializa o range nos params `from`/`to` — o handler implementa o recorte.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
periods: [
|
|
43
|
+
{ value: 'today', label: 'Hoje' },
|
|
44
|
+
{ value: 'last7', label: 'Últimos 7 dias' },
|
|
45
|
+
{ value: 'thisMonth', label: 'Este mês' },
|
|
46
|
+
]
|
|
47
|
+
// Presets computáveis: today · yesterday · last7 · last30 · thisMonth · lastMonth.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Paginação e loading
|
|
51
|
+
|
|
52
|
+
O pattern manda `limit` (= `pageSize`, default 50) e `page` no input; o handler implementa o OFFSET e devolve `total` no Paginated. O rodapé (Página X de Y · páginas numeradas com reticências · "N itens no total") aparece sempre que há total; mudar filtro/busca/sort/período volta pra página 1 (a página vive na URL: `?page=2`). No refetch com a lista já na tela, o corpo esmaece (`aria-busy`) até os dados chegarem — o skeleton é só do primeiro load.
|
|
53
|
+
|
|
54
|
+
## Multi-seleção com can (batch)
|
|
55
|
+
|
|
56
|
+
`batch` liga a coluna de checkbox na tabela. O `can(item)` é a fonte única de elegibilidade: linha inelegível não marca, o "selecionar todos" pega só os elegíveis, o botão mostra quantos dos selecionados valem e o `run` recebe apenas esses. Com `confirm`, um dialog pede confirmação; ao resolver, a seleção limpa e a lista refaz.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<ActionList action={runList} input={{}}
|
|
60
|
+
batch={[{
|
|
61
|
+
label: 'Reprocessar',
|
|
62
|
+
can: (run) => run.status === 'failed',
|
|
63
|
+
confirm: { title: 'Reprocessar as execuções?' },
|
|
64
|
+
run: async (runs) => { await Promise.all(runs.map((r) => api.retry(r.id))) },
|
|
65
|
+
}]}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Views (a mesma listagem, outro renderer)
|
|
70
|
+
|
|
71
|
+
Nem toda listagem é tabela: board, galeria, lista, calendário… A view é APRESENTAÇÃO de quem chama (`render`); o pattern dá o segment na toolbar (ToggleGroup, ICON-ONLY: declare `icon` — o label vira title/aria; sem ícone cai pro texto), o estado (`view` na URL) e os dados — a mesma fonte, os mesmos filtros. A tabela ('Tabela') participa quando há columns.
|
|
72
|
+
|
|
73
|
+
```tsx preview col
|
|
74
|
+
render(
|
|
75
|
+
<DocBrowserActionProvider>
|
|
76
|
+
<div className="w-full">
|
|
77
|
+
<ActionList
|
|
78
|
+
action={docWorkspaceList}
|
|
79
|
+
input={{}}
|
|
80
|
+
views={{
|
|
81
|
+
gallery: {
|
|
82
|
+
label: 'Galeria',
|
|
83
|
+
icon: <LayoutGrid />,
|
|
84
|
+
render: (workspaces) => (
|
|
85
|
+
<div className="grid grid-cols-2 gap-3">
|
|
86
|
+
{workspaces.map((w) => (
|
|
87
|
+
<div key={w.id} className="rounded-lg border border-border p-3">
|
|
88
|
+
<div className="flex items-center gap-2">
|
|
89
|
+
<span className="text-sm font-semibold">{w.name}</span>
|
|
90
|
+
<Badge variant={w.status === 'active' ? 'success' : 'warning'}>{w.status}</Badge>
|
|
91
|
+
</div>
|
|
92
|
+
<p className="mt-1 text-xs text-muted-foreground">{w.agents} agentes.</p>
|
|
93
|
+
</div>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
</DocBrowserActionProvider>,
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Filtros dependentes, dictionary e lookup
|
|
106
|
+
|
|
107
|
+
Três recursos do FilterSpec que a toolbar honra:
|
|
108
|
+
|
|
109
|
+
- **`depends: ['outroFiltro']`** — o filtro dependente fica DESABILITADO até o pai ter valor, e mudar o pai limpa o dependente em cascata (o recorte perde o sentido quando o pai muda). Vale inline e no modal.
|
|
110
|
+
- **`options: { kind: 'dictionary', ref: 'sessionStatus' }`** — as opções vêm de um DICT registrado no `<TbdlibProvider dicts={{ sessionStatus: statusDict }}>` (o `DictType` do `t.dict` encaixa direto). Labels do vocabulário no filtro E nos chips; runtime (`filterOptions`) sobrepõe.
|
|
111
|
+
- **`options: { kind: 'lookup', source: 'x.lookup' }`** — as opções vêm de uma ACTION (server-side): o campo vira Select typeahead que chama a `source` (debounced) com `{ q }` — e com os valores dos `depends` no input. Convenção: a action de lookup devolve itens `{ value, label }`. Com valor aplicado mas opções ainda não carregadas (URL, chip), o chip mostra o próprio value.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
filters: {
|
|
115
|
+
project: { label: 'Projeto', type: 'select', options: { kind: 'static', items: [...] } },
|
|
116
|
+
sprint: {
|
|
117
|
+
label: 'Sprint',
|
|
118
|
+
type: 'lookup',
|
|
119
|
+
depends: ['project'],
|
|
120
|
+
options: { kind: 'lookup', source: 'sprint.lookup', depends: ['project'] },
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Exibição e URL sync
|
|
126
|
+
|
|
127
|
+
O botão de exibição (engrenagem) abre o popover de configuração da listagem — presente em QUALQUER view: colunas (liga/desliga, incluindo as `hidden` do contrato; só com a tabela ativa) e itens por página (o default do caller fica fora da URL) — e é a casa do que vier depois. E o estado inteiro da toolbar (q, sort, filtros, view, colunas, limit) serializa pra querystring com os helpers padrão:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<ActionList
|
|
131
|
+
action={contract}
|
|
132
|
+
input={{}}
|
|
133
|
+
state={listParamsToState(params, contract)}
|
|
134
|
+
onStateChange={(next) => navigate(`/rota${listStateToParams(next, contract)}`)}
|
|
135
|
+
/>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Convenção compacta (defaults omitidos): `?q=…&sort=chave:dir&status=…&view=board&cols=a,b,c&limit=25`.
|
|
139
|
+
|
|
140
|
+
## Composição (layout livre)
|
|
141
|
+
|
|
142
|
+
Com children, a tabela sai de cena e o layout dos itens é seu (cards, grid, o que for) — a toolbar declarativa e os estados (skeleton/erro/vazio) seguem daqui. Mesmo princípio do ActionForm com children.
|
|
143
|
+
|
|
144
|
+
```tsx preview col
|
|
145
|
+
render(
|
|
146
|
+
<DocBrowserActionProvider>
|
|
147
|
+
<div className="w-full">
|
|
148
|
+
<ActionList action={docWorkspaceList} input={{}}>
|
|
149
|
+
{(workspaces) => (
|
|
150
|
+
<div className="grid w-full grid-cols-2 gap-3">
|
|
151
|
+
{workspaces.map((w) => (
|
|
152
|
+
<div key={w.id} className="rounded-lg border border-border p-3">
|
|
153
|
+
<div className="flex items-center gap-2">
|
|
154
|
+
<span className="text-sm font-semibold">{w.name}</span>
|
|
155
|
+
<Badge variant={w.status === 'active' ? 'success' : 'warning'}>{w.status}</Badge>
|
|
156
|
+
</div>
|
|
157
|
+
<p className="mt-1 text-xs text-muted-foreground">{w.agents} agentes.</p>
|
|
158
|
+
</div>
|
|
159
|
+
))}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
</ActionList>
|
|
163
|
+
</div>
|
|
164
|
+
</DocBrowserActionProvider>,
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## No contrato (ListAction)
|
|
169
|
+
|
|
170
|
+
| Chave | O que declara |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat? }` — a tabela. `hidden` fica fora (base do futuro column picker). |
|
|
173
|
+
| `filters` | `{ [nome]: { label, type, options?, multiple?, advanced?, depends?, … } }` — a toolbar. `advanced` vai pro modal; `depends` desabilita/cascateia; options `kind: 'lookup'` busca numa action. Valor aplicado entra no input com o MESMO nome. |
|
|
174
|
+
| `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Com período/filtros no contrato ela fica à direita; sendo a ÚNICA forma de recorte, abre a linha. |
|
|
175
|
+
| `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
|
|
176
|
+
| `periods` | `{ value, label }[]` — o controle de período (presets + Personalizado com calendário); materializa em `from`/`to` no input. |
|
|
177
|
+
|
|
178
|
+
## Props
|
|
179
|
+
|
|
180
|
+
| Prop | Tipo | Default | Descrição |
|
|
181
|
+
|---|---|---|---|
|
|
182
|
+
| `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item, paginate cursor). |
|
|
183
|
+
| `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
|
|
184
|
+
| `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
|
|
185
|
+
| `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime pros filtros select/lookup (chave = nome do filtro). |
|
|
186
|
+
| `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
|
|
187
|
+
| `children` | `(items, refetch) => ReactNode` | | Modo COMPOSIÇÃO: layout livre; a toolbar segue. Tem precedência sobre columns. |
|
|
188
|
+
| `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?, destructive? }` — liga a multi-seleção. |
|
|
189
|
+
| `rowId` | `(item) => string` | `item.id` | Identidade da linha pra seleção. |
|
|
190
|
+
| `pageSize` | `number` | `50` | Itens por página DEFAULT (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
|
|
191
|
+
| `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — pra quem embala sincronizar com a URL. |
|
|
192
|
+
| `emptyMessage` | `string` | `'Nenhum resultado.'` | O texto do estado vazio. |
|
|
193
|
+
| `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar pro detalhe) — só na tabela. |
|
|
194
|
+
| `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita) — apresentação de quem chama, como `cells`; cliques ali não disparam o `onRowClick`. |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
## Disparo direto
|
|
2
|
+
|
|
3
|
+
Sem confirm, dispara no clique: desabilita enquanto roda e toca o toast do contrato
|
|
4
|
+
(messages.success/error). O label default vem do action.label.
|
|
5
|
+
|
|
6
|
+
```tsx preview
|
|
7
|
+
<DocBrowserActionProvider>
|
|
8
|
+
<ActionTrigger action={docSessionArchive} input={{ id: 'onboarding-faturamento' }} variant="outline" />
|
|
9
|
+
</DocBrowserActionProvider>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Confirmação declarada no contrato
|
|
13
|
+
|
|
14
|
+
O ConfirmSpec mora na action (title/message/destructive) — o pattern monta o Dialog sozinho e o
|
|
15
|
+
destructive já tonaliza o botão. Prop confirm sobrepõe quando a tela precisar de outro texto.
|
|
16
|
+
|
|
17
|
+
```tsx preview
|
|
18
|
+
<DocBrowserActionProvider>
|
|
19
|
+
<ActionTrigger action={docSessionDelete} input={{ id: 'onboarding-faturamento' }} />
|
|
20
|
+
</DocBrowserActionProvider>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
// No contrato (spec):
|
|
25
|
+
confirm: {
|
|
26
|
+
title: 'Excluir a sessão?',
|
|
27
|
+
message: 'O worktree e o preview desta sessão serão removidos.',
|
|
28
|
+
confirmLabel: 'Excluir',
|
|
29
|
+
destructive: true,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Na tela — nada além do disparo:
|
|
33
|
+
<ActionTrigger action={sessionDeleteContract} input={{ id: session.id }} />
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## A ação que mora no item (icon)
|
|
37
|
+
|
|
38
|
+
Com `icon`, o botão vira icon-only: o `label` (ou o `action.label`) migra pro tooltip e pro `aria-label`, o visual fica discreto (ghost, e vermelho no hover quando o contrato marca `destructive`) e o clique **não vaza** pro item em volta — é o que permite viver dentro de uma linha ou card clicável. `itemLabel` nomeia o alvo na pergunta.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<ActionTrigger
|
|
42
|
+
action={unitDeleteContract}
|
|
43
|
+
input={{ id: unit.id }}
|
|
44
|
+
icon={<Trash2 />}
|
|
45
|
+
label="Excluir"
|
|
46
|
+
itemLabel={unit.name}
|
|
47
|
+
/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> Isto absorveu o antigo `DeleteButton` (7.0.0), que era exatamente este componente com uma lixeira dentro. Se você tinha `<DeleteButton action input itemLabel />`, troque por `<ActionTrigger … icon={<Trash2 />} />` — **e confira se o contrato declara `confirm`**: sem ele o disparo é direto, e o DeleteButton perguntava sempre.
|
|
51
|
+
|
|
52
|
+
## Erro que a pessoa entende
|
|
53
|
+
|
|
54
|
+
Quando a action falha, a frase do SERVIDOR só vence o rótulo do contrato se o erro for de negócio — `conflict`, `validation`, `not_found`:
|
|
55
|
+
|
|
56
|
+
> Este agente tem 3 conversas — desabilite em vez de excluir.
|
|
57
|
+
|
|
58
|
+
Isso é acionável. Erro inesperado carrega texto técnico cru: sem a allowlist, uma violação de FK viraria `violates foreign key constraint "user_unit_id_fkey"` no toast de quem só clicou num botão. Por isso allowlist e não denylist — categoria desconhecida cai no rótulo genérico, que é o lado seguro de errar.
|
|
59
|
+
|
|
60
|
+
## Props
|
|
61
|
+
|
|
62
|
+
| Prop | Tipo | Default | Descrição |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `action` | `SimpleContract<TInput, TData>` | | A SimpleAction do Opus — label, messages e confirm vêm do contrato. |
|
|
65
|
+
| `input` | `TInput` | | O que a action recebe — geralmente { id }. |
|
|
66
|
+
| `label` | `string` | `action.label` | Sobrepõe o texto do botão. |
|
|
67
|
+
| `variant / size` | `variants do Button` | `'default' (ou 'destructive' se o confirm declarar) / 'default'` | Visual do botão — o destructive do ConfirmSpec já escolhe sozinho. |
|
|
68
|
+
| `confirm` | `{ title, description?, actionLabel?, cancelLabel? }` | | Confirmação via prop — sobrepõe o ConfirmSpec do contrato. |
|
|
69
|
+
| `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
|
|
70
|
+
| `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza pro item. |
|
|
71
|
+
| `itemLabel` | `string` | | Nome do alvo na pergunta (sai entre aspas, em destaque, antes da mensagem do contrato). |
|
|
72
|
+
| `className` | `string` | | Classes do botão (ex.: apertar o tamanho numa linha densa). |
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
## Detalhe com estados padronizados
|
|
2
|
+
|
|
3
|
+
Troque o workspace pra ver o skeleton; o inexistente mostra o erro padrão com o Tentar de novo. O
|
|
4
|
+
preview roda num client de mentira — no app, cada feature embrulha (TicketView…) delegando o
|
|
5
|
+
fetching pra cá.
|
|
6
|
+
|
|
7
|
+
```tsx preview col
|
|
8
|
+
const [id, setId] = useState('empresa-x')
|
|
9
|
+
|
|
10
|
+
render(
|
|
11
|
+
<DocBrowserActionProvider>
|
|
12
|
+
<div className="w-full space-y-3">
|
|
13
|
+
<div className="flex flex-wrap gap-2">
|
|
14
|
+
<Button variant="outline" size="sm" onClick={() => setId('empresa-x')}>Empresa X</Button>
|
|
15
|
+
<Button variant="outline" size="sm" onClick={() => setId('softize-multica')}>Softize · Multica</Button>
|
|
16
|
+
<Button variant="outline" size="sm" onClick={() => setId('sessao-fantasma')}>Inexistente (erro)</Button>
|
|
17
|
+
</div>
|
|
18
|
+
<div className="rounded-lg border border-border p-4">
|
|
19
|
+
<ActionView action={docWorkspaceView} input={{ id }}>
|
|
20
|
+
{(ws) => (
|
|
21
|
+
<div className="space-y-1.5">
|
|
22
|
+
<div className="flex items-center gap-2">
|
|
23
|
+
<h3 className="text-sm font-semibold">{ws.name}</h3>
|
|
24
|
+
<Badge variant={ws.status === 'active' ? 'success' : 'warning'}>{ws.status}</Badge>
|
|
25
|
+
</div>
|
|
26
|
+
<p className="text-sm text-muted-foreground">
|
|
27
|
+
Cliente {ws.client} · {ws.agents} agentes vinculados.
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
)}
|
|
31
|
+
</ActionView>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</DocBrowserActionProvider>,
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Props
|
|
39
|
+
|
|
40
|
+
| Prop | Tipo | Default | Descrição |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `action` | `ViewAction<TInput, TData>` | | A ViewAction do Opus (kind view, 1 recurso). |
|
|
43
|
+
| `input` | `TInput` | | Geralmente { id } — mudou, recarrega. |
|
|
44
|
+
| `children` | `(data: TData, refetch) => ReactNode` | | O estado feliz — layout 100% do consumidor; refetch pra recarregar por código. |
|
|
45
|
+
| `render` | `(data: TData, refetch) => ReactNode` | | Alias de children (a API original). 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. |
|