@loom-forge/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,552 @@
1
+ /** Intenção declarativa: um evento do componente (`onClick`, `onSave`…) → uma action.
2
+ * O caminho CANÔNICO de efeito (events-out). Serializável; o componente não pega serviço. */
3
+ interface EventBinding {
4
+ action: string;
5
+ /**
6
+ * Os params da ação — e é POR AQUI que entra tudo o que a intenção recebe (ADR-018).
7
+ *
8
+ * Dentro deles a raiz `event` é o valor recebido: o que o widget emitiu num `events.onX`, o
9
+ * `data` da ação anterior num `then`, o `error` num `catch`. O autor escreve o mapeamento
10
+ * (`{ campo: "cpf", valor: "{{ event }}" }`, `{ valido: "{{ event.valido }}" }`) — nada entra
11
+ * costurado por fora. O que chega de um widget é o **valor** que o componente registrado emite,
12
+ * declarado em `Descriptor.meta.emits`, não o evento do DOM.
13
+ *
14
+ * São avaliados no DESPACHO, com o escopo do render mais `event` — não no render.
15
+ */
16
+ params?: unknown;
17
+ /**
18
+ * O que roda quando esta intenção RESOLVE: com `success: true` num despacho ao host, e
19
+ * `event` = `data`; depois do estado escrito num update local, e `event` = `undefined`.
20
+ */
21
+ then?: EventBinding;
22
+ /**
23
+ * O que roda quando o despacho ao host FALHA (`success: false` — falha de domínio ou o
24
+ * `UNHANDLED_ERROR` de um port que lança), com `event` = `error`. Sem `catch`, a falha vai só
25
+ * ao `onActionResult` do host, como sempre foi. Um fluxo cancelado (`aborted` com sucesso) não
26
+ * é resultado e não passa por aqui; um port que lançou é falha, e passa.
27
+ */
28
+ catch?: EventBinding;
29
+ /**
30
+ * Os params que o autor deixa DELIBERADAMENTE para a camada de comando preencher.
31
+ *
32
+ * Um comando resolve param por token (`$focus`), por default, ou perguntando — abrindo um
33
+ * drawer, um seletor. Isso é legítimo, e é diferente de esquecer: sem declarar, um obrigatório
34
+ * ausente é erro de build, e **tem de continuar sendo**. Trocar prova por sorte no default é o
35
+ * que a declaração impede.
36
+ *
37
+ * Quem preenche é o commander, com os resolvers dele; aqui só se declara a intenção — e ela
38
+ * fica visível na inspeção, que é o ponto.
39
+ */
40
+ resolve?: readonly string[];
41
+ /**
42
+ * A intenção é um UPDATE do componente que a escreveu — chamada interna, que não sai pela porta.
43
+ *
44
+ * **Não é campo de autoria.** Quem marca é a resolução (`resolveIntent`), que sabe de qual
45
+ * componente o nó é; uma marca escrita no documento é descartada ali. É ela que diz à view para
46
+ * entregar a intenção ao componente vivo em vez do `ActionPort` — e é por ser marca, e não nome,
47
+ * que o evento de um componente puro inlinado no corpo de um vivo não alcança os updates dele.
48
+ */
49
+ local?: true;
50
+ /**
51
+ * O ESCOPO do render em que o nó foi ligado — o que os `params` enxergam no despacho.
52
+ *
53
+ * **Não é campo de autoria.** Quem escreve é o `bind`, quando os `params` têm expressão: eles
54
+ * ficam compilados no nó resolvido e são avaliados no despacho, sobre este escopo mais `event`.
55
+ * Sem expressão nos `params`, não há escopo a guardar.
56
+ */
57
+ scope?: Readonly<Record<string, unknown>>;
58
+ }
59
+ /**
60
+ * O que um nome de comando executa: a ação, e o que já vem preenchido.
61
+ *
62
+ * A forma curta (`"submeter": "form/submit"`) é o caso comum; a longa existe para a **aplicação
63
+ * parcial** — a sessão liga parte dos params, e quem dispara só passa o que sobrou.
64
+ */
65
+ type CommandAlias = string | {
66
+ action: string;
67
+ params?: unknown;
68
+ };
69
+ /**
70
+ * De onde um nó veio: o componente cujo documento o escreveu, e o id que o autor deu a ele.
71
+ *
72
+ * Um nó resolvido não tem mais o id autorado — a expansão o prefixa por instância, e o `each` o
73
+ * renomeia por cópia. A origem é o que liga a TELA de volta ao DOCUMENTO.
74
+ */
75
+ interface NodeOrigin {
76
+ component: string;
77
+ id: string;
78
+ }
79
+ /** Um nó da definição declarativa (entrada). */
80
+ interface DefNode {
81
+ /** identidade ESTÁVEL — keys, endereçamento (JSON Pointer), mutação (json-myers). */
82
+ id: string;
83
+ /** discriminador → resolvido contra o registry de descriptors. Ausente quando `el` define
84
+ * uma tag intrínseca. */
85
+ type?: string;
86
+ /** TAG intrínseca: `{ el: "div", props, children }` = `createElement("div", props, children)`.
87
+ * Bypassa o registry; o tier mais baixo (DESIGN §6), "sob sua responsabilidade". Tem precedência
88
+ * sobre `type`, é CONSUMIDO na normalização (sai limpo no IR/ResolvedNode → vira `type` + `el`) e
89
+ * NUNCA cria aresta de dep no grafo. Compõe com children/layout/events normalmente. */
90
+ el?: string;
91
+ /** props brutas (após o bind). */
92
+ props?: Record<string, unknown>;
93
+ /** layout adapter que arruma os filhos deste nó. Default: o do descriptor, senão o do engine. */
94
+ layout?: string;
95
+ children?: DefNode[];
96
+ /** posição deste nó no layout do pai — opaca pro core, lida pelo planner. */
97
+ placement?: unknown;
98
+ /** forward-compat: se `type` não resolve, usa isto. `"drop"` = nada. */
99
+ fallback?: "drop" | DefNode;
100
+ /** gating de capability: `cap → versão mínima`, ou `cap → "*"` (só presença).
101
+ * Checado contra as capabilities anunciadas pelo host. */
102
+ requires?: Record<string, string>;
103
+ version?: string;
104
+ /** eventos declarativos: `nomeDaProp → intenção` (ex.: `onSave → { action, params }`). */
105
+ events?: Record<string, EventBinding>;
106
+ /** DIRETIVA estrutural: repete este nó por item da coleção (array) ou re-escopa a subárvore
107
+ * (objeto). Expande 1:N no bind — não liga valor. Consumida (não aparece na saída). */
108
+ each?: unknown;
109
+ /** o NOME que o `each` liga para o item corrente. Identificador, não expressão. */
110
+ as?: string;
111
+ /**
112
+ * DIRETIVA de escopo: instala contextos para a SUBÁRVORE — `nome → expressão`.
113
+ *
114
+ * É o `<Ctx.Provider value={…}>` escrito como diretiva, e por isso **qualquer nó provê**:
115
+ * um componente-provedor seria um nó que existe só por mecanismo. O nó provê para BAIXO,
116
+ * não para si — as props e o `when` dele são avaliados no contexto herdado, como os
117
+ * atributos de um Provider são avaliados por fora. A exceção é o nó que é CHAMADA de
118
+ * componente custom: ali o corpo do chamado É a subárvore, e enxerga.
119
+ */
120
+ provides?: Record<string, unknown>;
121
+ /**
122
+ * DIRETIVA de escopo: o vocabulário de COMANDOS da subárvore — `nome local → ação`.
123
+ *
124
+ * É o mesmo mecanismo do `provides`, no eixo do comportamento: o que viaja é **nome**, nunca
125
+ * função (contrato tem de ser dado — ver `forge/docs/CONTEXT.md`). Uma sessão de formulário
126
+ * declara `limpar`, `autopreencher` e `submeter`, e o mesmo botão composto em outra sessão
127
+ * executa outro `submeter`. Contrato, não acoplamento.
128
+ *
129
+ * A resolução é léxica e tem três degraus: **update local do componente → escopo de comando
130
+ * mais próximo → porta raiz**. O update ganha porque ele não sai do componente; o alias ganha
131
+ * do host porque ele é mais perto.
132
+ */
133
+ commands?: Record<string, CommandAlias>;
134
+ /** DIRETIVA estrutural: dropa o nó se a expressão for falsy. Dentro de um `each`, é
135
+ * avaliada por item (filtra). Consumida (não aparece na saída). */
136
+ when?: unknown;
137
+ /**
138
+ * De onde o nó veio, do mais de FORA para o mais de dentro.
139
+ *
140
+ * É uma lista porque um nó pode estar no lugar de mais de um: o corpo de um componente OCUPA o
141
+ * nó de chamada, e a raiz do corpo responde pelos dois. **Não é campo de autoria** — quem escreve
142
+ * é a expansão do `forge`, e ela passa até o `ResolvedNode`.
143
+ */
144
+ origins?: readonly NodeOrigin[];
145
+ }
146
+ /** Metadado HEADLESS de um `type` — nunca contém o componente (isso é da camada de render). */
147
+ interface Descriptor {
148
+ type: string;
149
+ /** props default (mescladas sob as props do nó). */
150
+ defaultProps?: Record<string, unknown>;
151
+ /** schema das props (JSON Schema / Schema do TSLite) — validação & form-builder. */
152
+ schema?: unknown;
153
+ /** layout default pros filhos deste type. */
154
+ layout?: string;
155
+ /** metadados de editor (label/categoria/ícone…). */
156
+ meta?: Record<string, unknown>;
157
+ }
158
+ interface DescriptorRegistry {
159
+ register(descriptor: Descriptor): DescriptorRegistry;
160
+ register(type: string): DescriptorRegistry;
161
+ get(type: string): Descriptor | undefined;
162
+ has(type: string): boolean;
163
+ list(): Descriptor[];
164
+ }
165
+ /** Saída do pipeline — árvore RESOLVIDA, dados puros, agnóstica de ambiente.
166
+ * Qualquer renderer (React/HTML/RN) consome isto; o core não conhece nenhum. */
167
+ type ResolvedNode = {
168
+ kind: "node";
169
+ id: string;
170
+ type: string;
171
+ /** quando `true`, `type` é uma tag intrínseca → a view usa `createElement(type, …)`. */
172
+ el?: boolean;
173
+ props: Record<string, unknown>;
174
+ /** intenções declarativas (events-out) — a view materializa em callbacks via ActionPort. */
175
+ events?: Record<string, EventBinding>;
176
+ /** plano de arrumação dos filhos (calculado pelo planner). */
177
+ layout?: ResolvedLayout;
178
+ children: ResolvedNode[];
179
+ /** de onde o nó veio — ver `DefNode.origins`. */
180
+ origins?: readonly NodeOrigin[];
181
+ } | {
182
+ kind: "drop";
183
+ id: string;
184
+ } | {
185
+ kind: "unknown";
186
+ id: string;
187
+ type: string;
188
+ };
189
+ interface ResolvedLayout {
190
+ name: string;
191
+ /** dados do plano — pareado por `name` com o renderer da camada de view. */
192
+ plan: unknown;
193
+ }
194
+
195
+ /** Bind: expande a definição contra dados (pré-passo). Adapter alvo: TSLite. */
196
+ interface BindingEngine {
197
+ /**
198
+ * Avalia a definição COMPILADA contra o escopo. O segundo argumento é o mapa de RAÍZES
199
+ * — `{ data }` no render, `{ props, state }` no corpo de um componente. Nada entra no
200
+ * escopo implicitamente: o resto dos nomes é declarado pelo `as` de um `each`.
201
+ */
202
+ bind(node: DefNode, scope: unknown): DefNode;
203
+ /**
204
+ * Traduz a definição AUTORADA para a forma que este binder consome — em geral, expressão
205
+ * em texto vira expressão em árvore. **Idempotente e memoizada por identidade**, então
206
+ * chamar de novo é barato e chamar sobre já-compilado é no-op.
207
+ *
208
+ * Ausente = o binder consome o autorado direto (é o caso do `identityBinder`).
209
+ *
210
+ * As duas fases ficam separadas de propósito: compilar pode acontecer no servidor, no save
211
+ * ou no build, e o resultado é dado serializável. Quem chama é o PONTO DE ENTRADA (o
212
+ * `resolveTree`, a expansão do forge), não o `bind` por dentro — um `bind` que compilasse
213
+ * escondido apagaria a fronteira que torna a consolidação possível.
214
+ */
215
+ compile?(node: DefNode): DefNode;
216
+ }
217
+ declare const identityBinder: BindingEngine;
218
+ /** Validação de um valor contra um schema. Adapter alvo: AJV / Zod (escolha do consumidor). */
219
+ interface ValidationIssue {
220
+ code: string;
221
+ path?: string;
222
+ message?: string;
223
+ }
224
+ interface ValidationProvider {
225
+ validate(schema: unknown, value: unknown): ValidationIssue[] | null;
226
+ }
227
+ /** Default: sem validação. */
228
+ declare const noopValidator: ValidationProvider;
229
+ /**
230
+ * O ENVELOPE de um despacho. **`invoke` nunca lança por erro de domínio** — falha é DADO.
231
+ *
232
+ * `code` é o contrato do erro: discrimine por ele, nunca por regex na mensagem.
233
+ */
234
+ interface ActionResult<T = unknown> {
235
+ readonly success: boolean;
236
+ /** o fluxo foi interrompido (vs. completou)? */
237
+ readonly aborted?: boolean;
238
+ readonly abortedBy?: string;
239
+ readonly data?: T;
240
+ readonly error?: {
241
+ readonly code: string;
242
+ readonly message?: string;
243
+ readonly data?: unknown;
244
+ };
245
+ }
246
+ /**
247
+ * O que um `invoke` devolve.
248
+ *
249
+ * **Async é o piso por física**, não por gosto: async embrulha sync, sync não embrulha async. Um
250
+ * host que não tem o que dizer devolve `void`, e quem normaliza é o `dispatchIntent`.
251
+ */
252
+ type ActionOutcome = void | ActionResult | Promise<void | ActionResult>;
253
+ /** Execução de ações da aplicação — o destino do events-out. */
254
+ interface ActionPort {
255
+ /** dispara. Domínio falho volta como `{ success: false, error }`; não lança. */
256
+ invoke(id: string, params?: unknown, ctx?: unknown): ActionOutcome;
257
+ has?(id: string): boolean;
258
+ /** pode rodar agora? Alimenta o `computeUnable` e o estado desabilitado de um botão. */
259
+ isAvailable?(id: string): boolean;
260
+ /** a FORMA dos params, em JSON Schema — é o que tipa o call-site de um evento. */
261
+ paramsOf?(id: string): unknown;
262
+ /** a forma do RETORNO, em JSON Schema — é o que tipa "o retorno vira estado". */
263
+ resultOf?(id: string): unknown;
264
+ /** rótulo, ícone, categoria, atalho — o que uma paleta mostra. */
265
+ metadataOf?(id: string): Record<string, unknown> | undefined;
266
+ /**
267
+ * **Quanto vale** cada resposta do Tier 1 — `"inferred" | "declared" | "verified" | "none"`.
268
+ *
269
+ * Não é enfeite: uma assinatura `declared` pode divergir do que a implementação de fato aceita,
270
+ * e a única prova que depende dela ser COMPLETA é "esta chave não existe". Por isso o `check`
271
+ * usa isto para decidir se um `excess-property` é erro ou aviso.
272
+ */
273
+ provenanceOf?(query: string): string;
274
+ }
275
+ /** Endereço/diff/patch do documento (editor). Adapter alvo: json-myers (ou RFC 6902). */
276
+ interface AddressPort {
277
+ diff(a: unknown, b: unknown): unknown;
278
+ patch(base: unknown, diff: unknown): unknown;
279
+ }
280
+ /** Checagem de tipos — compat `action.return ≤ component.props`, bindings. Adapter: @tslite/type-core. */
281
+ interface TypeChecker {
282
+ /** `a` é atribuível a `b`? */
283
+ subtype(a: unknown, b: unknown): boolean;
284
+ }
285
+ /** ESCAPE imperativo: capabilities vivas do host (router, mapa, ws, focus-handle…) que NÃO
286
+ * cabem em data-in/events-out. O caminho canônico é via ActionPort; isto é a exceção.
287
+ * Pode ser preenchido pelo kernel/providers do host. */
288
+ interface CapabilityPort {
289
+ get(name: string): unknown;
290
+ has(name: string): boolean;
291
+ }
292
+
293
+ /** O que o despacho de uma cadeia precisa de quem a executa — a view, o boundary. */
294
+ interface IntentRuntime {
295
+ /** avalia os `params` de UMA intenção: o escopo dela (o do render) mais `event`. */
296
+ params(binding: EventBinding, event: unknown): unknown;
297
+ /** aplica um update local — o componente vivo. Ausente = não há boundary aqui. */
298
+ apply?(action: string, params: unknown): void;
299
+ /** o que fazer com uma intenção local sem boundary — em dev, avisar. */
300
+ orphan?(action: string): void;
301
+ port?: ActionPort;
302
+ onResult?(id: string, result: ActionResult): void;
303
+ /** o pendente da instância: `true` do despacho ao resultado. Ausente = não há instância. */
304
+ pending?(action: string, flag: boolean): void;
305
+ }
306
+ /**
307
+ * Executa uma intenção e a cadeia dela. Nunca devolve nada — o resultado é evento.
308
+ *
309
+ * Local: aplica e segue para o `then` com `event` = `undefined`. Host: despacha; no
310
+ * `success`, segue para o `then` com `data`; na falha, para o `catch` com `error`;
311
+ * `aborted` não é nenhum dos dois e fica só com o host.
312
+ */
313
+ declare function runIntent(binding: EventBinding, event: unknown, rt: IntentRuntime): void;
314
+ /**
315
+ * Dispara uma intenção e NUNCA devolve nada — nem promessa, nem valor.
316
+ *
317
+ * O resultado, sync ou async, vai para `onResult`. Falha de domínio chega como
318
+ * `{ success: false, error }`; um port que LANÇA vira `UNHANDLED_ERROR`, porque isso é bug de
319
+ * programa e não pode derrubar um clique.
320
+ */
321
+ declare function dispatchIntent(port: ActionPort | undefined, id: string, params?: unknown, onResult?: (id: string, result: ActionResult) => void): void;
322
+ /**
323
+ * Os `params` de uma intenção, avaliados no despacho — pelo MESMO caminho do render.
324
+ *
325
+ * O binder liga um nó-fantasma cuja prop é o `params` compilado, sobre o escopo que o `bind`
326
+ * guardou no binding mais a raiz `event`. Reusar o binder é o que garante o mesmo gate de
327
+ * capability, o mesmo orçamento e o mesmo escopo que o clique enxergava. Sem escopo guardado
328
+ * não há expressão nos `params`, e eles são dado como estão.
329
+ */
330
+ declare function intentParams(binder: BindingEngine, binding: EventBinding, event: unknown): unknown;
331
+ /** A intenção e cada elo da cadeia dela (`then`/`catch`, em profundidade), na ordem de escrita. */
332
+ declare function intentsOf(binding: EventBinding): EventBinding[];
333
+
334
+ /** O que está em vigor num ponto da árvore — nome local → ação. */
335
+ type CommandScope = Readonly<Record<string, CommandAlias>>;
336
+ declare const EMPTY_COMMANDS: CommandScope;
337
+ /**
338
+ * O escopo em vigor ABAIXO de um nó: o herdado com o que ele instala por cima.
339
+ *
340
+ * O sombreamento é literalmente o merge — o mais próximo ganha, como no `provides`.
341
+ */
342
+ declare const commandScope: (inherited: CommandScope, node: {
343
+ commands?: Record<string, CommandAlias>;
344
+ }) => CommandScope;
345
+ /** O alias normalizado: a forma curta é açúcar para `{ action }`. */
346
+ declare const aliasOf: (alias: CommandAlias) => {
347
+ action: string;
348
+ params?: unknown;
349
+ };
350
+ /**
351
+ * Resolve uma intenção contra o escopo — e a ordem é a do runtime.
352
+ *
353
+ * `updates` são os UPDATES do componente que ESCREVEU a intenção: um nome que resolve ali é
354
+ * chamada interna e **não** passa pelo escopo, porque ele não sai do componente (ADR-014). O que
355
+ * não é update nem alias segue como está, e vai parar na porta raiz.
356
+ *
357
+ * Os params do alias são **aplicação parcial**: eles entram por baixo, e o que o call-site passa
358
+ * ganha. Quem dispara é mais específico que quem declarou o vocabulário.
359
+ *
360
+ * O que termina num update — pelo nome, ou pelo alvo de um alias — sai MARCADO (`local`). A marca
361
+ * é a resposta, e não o nome: a view entrega ao componente vivo só o que foi marcado, então uma
362
+ * intenção de outro componente com o mesmo nome não alcança o update dele.
363
+ *
364
+ * `then` e `catch` são intenções, e resolvem pela mesma régua — cada elo da cadeia no mesmo
365
+ * escopo em que a cadeia foi escrita.
366
+ */
367
+ declare function resolveIntent(binding: EventBinding, scope: CommandScope, updates?: ReadonlySet<string>): EventBinding;
368
+
369
+ /** Registry ABERTO de descriptors (metadado headless por `type`). Encadeável.
370
+ * Aceita a forma completa (`Descriptor`) ou só o `type` (descriptor nu). */
371
+ declare function createDescriptorRegistry(initial?: Descriptor[]): DescriptorRegistry;
372
+
373
+ interface ResolveCtx {
374
+ registry: DescriptorRegistry;
375
+ }
376
+ type ResolveResult = {
377
+ kind: "render";
378
+ descriptor: Descriptor;
379
+ props: Record<string, unknown>;
380
+ } | {
381
+ kind: "drop";
382
+ } | {
383
+ kind: "unknown";
384
+ node: DefNode;
385
+ };
386
+ interface Resolver {
387
+ resolve(node: DefNode, ctx: ResolveCtx): ResolveResult;
388
+ }
389
+ interface TypeResolverOptions {
390
+ /** capacidades do host (capability → versão). Habilita o gating por `node.requires`. */
391
+ capabilities?: Record<string, string>;
392
+ }
393
+ /** Estratégia de resolução EXPLÍCITA (`node.type` → descriptor). Mescla `defaultProps`,
394
+ * aplica `requires` e a cadeia de `fallback`. */
395
+ declare function createTypeResolver(options?: TypeResolverOptions): Resolver;
396
+
397
+ interface PlannerChild {
398
+ id: string;
399
+ placement?: unknown;
400
+ }
401
+ interface LayoutPlanner {
402
+ name: string;
403
+ plan(children: PlannerChild[]): unknown;
404
+ /**
405
+ * A FORMA do `placement` que este layout lê, em JSON Schema — o contrato do filho.
406
+ *
407
+ * É o `descriptor.schema` do eixo de arrumação: um editor monta os campos de posição a partir
408
+ * dele, sem saber que layout é. Ausente = o layout não publica a forma, e quem edita fica com o
409
+ * objeto cru.
410
+ */
411
+ placementSchema?: unknown;
412
+ }
413
+ interface FragmentPlan {
414
+ order: string[];
415
+ }
416
+ declare function fragmentPlanner(): LayoutPlanner;
417
+ interface SectionPlan {
418
+ direction: "column" | "row";
419
+ gap?: string | number;
420
+ order: string[];
421
+ }
422
+ interface SectionOptions {
423
+ direction?: "column" | "row";
424
+ gap?: string | number;
425
+ }
426
+ declare function sectionPlanner(opts?: SectionOptions): LayoutPlanner;
427
+ /** span por breakpoint (mobile-first; cada nível herda o anterior se ausente). */
428
+ interface ResponsiveSpan {
429
+ base: number;
430
+ sm?: number;
431
+ md?: number;
432
+ lg?: number;
433
+ xl?: number;
434
+ }
435
+ /** spans preenchidos pra todos os breakpoints (forward-fill). */
436
+ interface FilledSpans {
437
+ base: number;
438
+ sm: number;
439
+ md: number;
440
+ lg: number;
441
+ xl: number;
442
+ }
443
+ interface GridFormCell {
444
+ id: string;
445
+ /** span no breakpoint base — back-compat + caminho inline (não-responsivo). */
446
+ span: number;
447
+ /** spans por breakpoint (forward-filled) — caminho responsivo. */
448
+ spans: FilledSpans;
449
+ break?: boolean;
450
+ }
451
+ interface GridFormPlan {
452
+ columns: number;
453
+ gap: string | number;
454
+ /** algum slot varia entre breakpoints? (decide inline vs CSS no renderer). */
455
+ responsive: boolean;
456
+ cells: GridFormCell[];
457
+ }
458
+ interface GridFormPlacement {
459
+ /** número (fixo) ou objeto `{ base, sm, md, lg, xl }` (responsivo). */
460
+ span?: number | ResponsiveSpan;
461
+ break?: boolean;
462
+ index?: number;
463
+ }
464
+ interface GridFormOptions {
465
+ columns?: number;
466
+ gap?: string | number;
467
+ }
468
+ declare function gridFormPlanner(opts?: GridFormOptions): LayoutPlanner;
469
+ /** planners default (`fragment` · `section` · `grid-form`). */
470
+ declare function defaultPlanners(): Record<string, LayoutPlanner>;
471
+
472
+ interface ResolveTreeOptions {
473
+ registry: DescriptorRegistry;
474
+ resolver?: Resolver;
475
+ binder?: BindingEngine;
476
+ planners?: Record<string, LayoutPlanner>;
477
+ /** layout usado quando o nó/descriptor não declara um. */
478
+ defaultLayout?: string;
479
+ }
480
+ /**
481
+ * O PIPELINE headless do COMPOSITOR: `bind` (pré-passo) → `resolve` (type→descriptor+props) →
482
+ * `plan` (layout). Produz uma `ResolvedNode` — DADOS puros. Compõe componentes REGISTRADOS +
483
+ * elementos intrínsecos (`el`) + data-binding. **Não conhece componente custom** — a expansão
484
+ * de `ComponentDef` é do construtor (`forge`), que entrega uma árvore pura pra cá.
485
+ *
486
+ * `el`: `type` vem da tag (`{ el: "div" }`) → `createElement(type, …)` na view (`el: true`).
487
+ * `fallback:"drop"` some da árvore E do plano; `unknown` permanece (a view degrada).
488
+ */
489
+ declare function resolveTree(node: DefNode, opts: ResolveTreeOptions, scope?: unknown): ResolvedNode;
490
+
491
+ /** Percorre em pré-ordem. `visit(node, parent)` — `parent` é `null` na raiz. */
492
+ declare function walk(node: DefNode, visit: (node: DefNode, parent: DefNode | null) => void, parent?: DefNode | null): void;
493
+ /** Primeiro nó com o `id` dado (busca em profundidade). */
494
+ declare function findById(root: DefNode, id: string): DefNode | undefined;
495
+ /** Caminho de ids da raiz ao alvo (inclusive), ou `null`. */
496
+ declare function pathToId(root: DefNode, id: string): string[] | null;
497
+ /** Mapeia a árvore imutavelmente, bottom-up (filhos já transformados quando `fn` roda). */
498
+ declare function mapNodes(node: DefNode, fn: (node: DefNode) => DefNode): DefNode;
499
+
500
+ /** O papel de um campo do `DefNode` na travessia. */
501
+ type FieldRole =
502
+ /** identidade — nunca tocada. */
503
+ "identity"
504
+ /** dado do documento: nome de tipo, tag, layout. Não é código, não é subárvore. */
505
+ | "data"
506
+ /** carrega nó(s) — a travessia desce por aqui. */
507
+ | "tree"
508
+ /** aceita EXPRESSÃO — ver `SLOTS`. */
509
+ | "slot";
510
+ declare const FIELDS: Readonly<Record<keyof DefNode, FieldRole>>;
511
+ /** Onde, dentro do campo, os valores compiláveis estão. */
512
+ type SlotShape =
513
+ /** o próprio valor do campo. */
514
+ "value"
515
+ /** um mapa de INTENÇÕES (`events`, `commands`); o valor é o `params` de cada entrada. */
516
+ | "intentParams";
517
+ interface SlotDecl {
518
+ shape: SlotShape;
519
+ /** o que o slot espera render. O checker usa isto como `resultType`; `unknown` = não restringe. */
520
+ expects: "boolean" | "unknown";
521
+ /**
522
+ * Em que estágio do bind o slot é resolvido.
523
+ *
524
+ * `structural` muda a ÁRVORE e é consumido (não aparece na saída) — `each` repete o nó,
525
+ * `when` o dropa. `value` liga o campo NO LUGAR. Sem isto, quem liga valores teria de
526
+ * lembrar de excluir os estruturais à mão, que é a forma de esquecimento que esta tabela
527
+ * existe para impedir.
528
+ */
529
+ stage: "value" | "structural";
530
+ /**
531
+ * Se `{% %}` e `{# #}` são marcação neste slot, além do `{{ }}`.
532
+ *
533
+ * O dado não se auto-declara: é o slot que diz o que uma string dele pode conter. Hoje
534
+ * nenhum slot aceita controle de fluxo — `{%` e `{#` são texto em todos —, e a coluna existe
535
+ * para que a compilação pergunte à tabela, e não a um padrão implícito.
536
+ */
537
+ directives: boolean;
538
+ }
539
+ declare const SLOTS: Readonly<Record<string, SlotDecl>>;
540
+ /** Este campo aceita expressão? Campo fora da tabela NÃO tem porta: expressão ali é erro,
541
+ * e a resposta sai sem avaliar nada. */
542
+ declare const isSlot: (field: string) => boolean;
543
+ /** A declaração do slot, ou `undefined` se o campo não é um. */
544
+ declare const slotOf: (field: string) => SlotDecl | undefined;
545
+ /** Como cada campo `tree` carrega nó: uma lista, ou um nó só. */
546
+ type TreeShape = "list" | "node";
547
+ /** Os campos por onde a travessia desce em busca de mais nós. Um campo `tree` novo entra aqui
548
+ * e todo mundo que anda a árvore passa a cobri-lo — travessia divergente é o bug que só
549
+ * aparece quando um campo novo entra. */
550
+ declare const TREE: Readonly<Record<string, TreeShape>>;
551
+
552
+ export { type ActionOutcome, type ActionPort, type ActionResult, type AddressPort, type BindingEngine, type CapabilityPort, type CommandAlias, type CommandScope, type DefNode, type Descriptor, type DescriptorRegistry, EMPTY_COMMANDS, type EventBinding, FIELDS, type FieldRole, type FilledSpans, type FragmentPlan, type GridFormCell, type GridFormOptions, type GridFormPlacement, type GridFormPlan, type IntentRuntime, type LayoutPlanner, type NodeOrigin, type PlannerChild, type ResolveCtx, type ResolveResult, type ResolveTreeOptions, type ResolvedLayout, type ResolvedNode, type Resolver, type ResponsiveSpan, SLOTS, type SectionOptions, type SectionPlan, type SlotDecl, type SlotShape, TREE, type TreeShape, type TypeChecker, type TypeResolverOptions, type ValidationIssue, type ValidationProvider, aliasOf, commandScope, createDescriptorRegistry, createTypeResolver, defaultPlanners, dispatchIntent, findById, fragmentPlanner, gridFormPlanner, identityBinder, intentParams, intentsOf, isSlot, mapNodes, noopValidator, pathToId, resolveIntent, resolveTree, runIntent, sectionPlanner, slotOf, walk };