# Praxis Table - Expandable Rows Enterprise Plan (V5 Execution-Aligned)

## 1. Objetivo

Consolidar um plano **enterprise-ready** para `expandable rows` na `praxis-table`, com:

1. paridade competitiva com AG Grid Enterprise, MUI DataGrid Premium, TanStack Table e Kendo UI;
2. diferencial `schema-first` (JSON + editor), com composição por metadados (`card/form/table/chart/richText/tabs/list`) e renderer registry;
3. governança de contrato/runtime/documentação/AI metadata para evitar drift;
4. segurança, privacidade, observabilidade e operação tratadas desde o início;
5. defaults de segurança aplicados no runtime (não apenas declarados em documentação).

---

## 2. Veredito Executivo do Plano

**Go com ressalvas moderadas para o plano completo.**

Recorte de execução recomendado para reduzir risco:

1. P0A: contrato + runtime não virtualizado + evento canônico + A11y base;
2. P0B: virtualizado fixo + collapse policies essenciais + observabilidade mínima;
3. P1: detail schema/lazy/deep-link seguro/editor completo;
4. P2: altura dinâmica em virtualização + perf avançada.

---

## 3. Baseline Técnico (estado atual da Praxis)

1. não existe `behavior.expansion` no contrato atual;
2. não há detail row no runtime atual;
3. há dois caminhos de render (`mat-table` e `cdk-virtual-scroll`);
4. virtualização atual usa `itemSize` fixo;
5. já existem fundações reutilizáveis:
   - persistência com ack;
   - warnings deduplicados;
   - avaliação DSL segura;
   - i18n e `aria-live`;
   - editor robusto para seções de comportamento.

## 3.1 Impacto cross-lib (obrigatório no plano)

`behavior.expansion` nasce no contrato de `@praxisui/core`, então rollout exige alinhamento conjunto:

1. model/types/defaults em `praxis-core`;
2. editor/runtime/docs/testes em `praxis-table`;
3. metadata de AI/context pack com os novos paths.

## 3.2 AS-IS vs TO-BE (Schema-Driven UI da tabela dinâmica)

| Camada | AS-IS (runtime atual) | TO-BE para expandable rows | Gap crítico |
| --- | --- | --- | --- |
| Contrato `TableConfig` | `TableBehaviorConfig` não possui `expansion` | Adicionar `behavior.expansion` com defaults e coerções fail-closed | Sem campo nativo, não há governança de estado de expansão |
| Resolução de modo de dados | `DataMode` já resolve `remote/local/empty`, com precedência `resourcePath > data > persistido` | Reutilizar resolução existente sem bypass na expansão | Evitar branch paralelo de estado que ignore `DataMode` |
| Pipeline local | `computeLocalViewPipeline` + `matchesAdvancedCriteria` já cobre filtro/sort/paginação | Integrar `collapseOn.*` e limites de expansão ao recálculo local | Risco de drift entre recálculo local e estado expandido |
| Filtro schema-driven | `praxis-filter` usa schema remoto ou `fieldMetadata` local derivado de colunas | Detail schema deve seguir mesma governança (validator compartilhado runtime/editor) | Duas engines de validação diferentes geram incoerência |
| Render surface | Dois caminhos ativos: `mat-table` e `cdk-virtual-scroll` | Expandir primeiro em `mat-table` (P0A) e depois virtualizado fixo (P0B) | Detail dinâmico no virtualizado quebra premissa de altura fixa |
| Segurança/privacidade | Já existe disciplina de schema/metadata e modo local com guard rails | Eventos/URL/persistência da expansão entram com redaction + canonicalização + anti-injection | Vazamento de IDs e state-injection se aplicar padrão antigo |
| Observabilidade | Adapter/sink e padrões de métrica já existem no ecossistema da tabela | Incluir namespace `praxis.table.expansion.*` no P0/P1 | Telemetria ad-hoc sem contrato aumenta risco operacional |
| Editor/IA | Editor de comportamento maduro + AI context pack para paths existentes | Nova seção `Expansion` + update de capabilities/context pack | IA continuará sugerindo contrato incompleto se não atualizar metadados |
| Documentação conceitual | JSON API da tabela é forte, mas link de conceito global (`schema-driven-ui.md`) não está resolvido localmente | Registrar conceito no workspace/documentação efetiva da lib | Onboarding e governança prejudicados por referência quebrada |

---

## 4. Matriz Estratégica (benchmark resumido)

| Recurso | AG Grid | MUI | TanStack | Kendo | Praxis alvo |
| --- | --- | --- | --- | --- | --- |
| Master/detail base | Forte | Forte | Flexível headless | Forte | Paridade |
| Estado controlado | Forte | Forte | Forte | Forte | Paridade |
| Virtualização + detail | Forte | Forte (com caveats) | Depende | Forte | Paridade com política explícita |
| Lazy detail | Forte | Forte | Depende | Forte | Paridade |
| A11y | Forte | Forte | Depende | Forte | Paridade |
| JSON no-code | Não foco | Não foco | Não foco | Não foco | Acima (diferencial) |

---

## 5. Decisões Mandatórias

1. contrato determinístico de estado/precedência;
2. política de virtualização formal (`fixed-height-only` no P0);
3. regra explícita de precedência de interação;
4. persistência robusta no P0 e deep-link seguro no P1;
5. round-trip obrigatório em model/editor/json-api/AI capabilities;
6. telemetria mínima operacional já no P0/P1 com adapter/sink explícito;
7. defaults de segurança ativos por padrão no runtime (fail-closed).
8. quando o backend expuser `_links.capabilities`, o detail row deve preferir o snapshot agregado canônico em vez de reintroduzir `resourcePath` ad hoc no host.

---

## 6. Contrato JSON Proposto (V5)

## 6.1 JSON alvo

```json
{
  "behavior": {
    "expansion": {
      "enabled": true,
      "state": {
        "mode": "controlled",
        "expandedRowKeys": [],
        "emitChanges": true,
        "sourceOfTruth": "input",
        "onSetExpandedKeys": "emitOnly"
      },
      "identity": {
        "rowKeySource": "table.idField",
        "requireStableIdField": true
      },
      "interaction": {
        "trigger": "icon",
        "toggleOnRowClick": false,
        "keyboard": {
          "profile": "disclosure",
          "enterSpace": true,
          "arrowLeftRight": false
        }
      },
      "limits": {
        "allowMultiple": false,
        "maxExpandedRows": 1,
        "onOverflow": "collapseOldest"
      },
      "collapseOn": {
        "sortChange": true,
        "pageChange": true,
        "filterChange": true,
        "dataRefresh": true,
        "groupChange": true,
        "columnVisibilityChange": true,
        "columnOrderChange": true,
        "columnResizeChange": true,
        "densityChange": true,
        "rowSelectionChange": false,
        "viewportDataWindowChange": false
      },
      "rowExpandableWhen": {
        "expr": "row.status != 'ARCHIVED'",
        "onError": "deny",
        "context": ["row", "computed"]
      },
      "detail": {
        "schemaContract": {
          "kind": "praxis.detail.schema",
          "version": "1.0.0",
          "compat": "semver",
          "allowedNodes": ["layout", "stack", "tabs", "tab", "card", "value", "action", "list", "formRef", "tableRef", "chartRef", "richText", "templateRef"],
          "sanitization": "strict"
        },
        "source": {
          "mode": "resource",
          "resource": {
            "kind": "ui-composition",
            "id": "order-detail-v1",
            "version": "1.0.0"
          },
          "contextMap": {
            "orderId": "$row.id",
            "tenantId": "$ctx.tenantId"
          },
          "fallbackMode": "inline",
          "inlineSchema": {
            "layout": "stack",
            "items": [
              { "type": "card", "title": "Resumo", "fields": [{ "label": "Pedido", "value": "$data.orderNumber" }] },
              { "type": "formRef", "schemaId": "order-edit-form-v2" },
              { "type": "tableRef", "schemaId": "order-items-table-v1" }
            ]
          }
        },
        "rendering": {
          "strategy": "registry",
          "registryId": "praxis.detail.default",
          "hostLayout": "auto",
          "fallbackNodePolicy": "failClosed"
        },
        "height": {
          "mode": "fixed",
          "px": 160
        },
        "lazyLoad": {
          "enabled": true,
          "actionId": "fetchOrderDetails",
          "cache": { "enabled": true, "ttlMs": 300000 },
          "retry": { "maxAttempts": 2 },
          "cancelOnCollapse": true,
          "dedupeByRowKey": true
        }
      },
      "virtualization": {
        "policy": "fixed-height-only"
      },
      "persistence": {
        "enabled": true,
        "storageKey": "expanded-rows",
        "storageKeyStrategy": {
          "namespace": "praxis.table.expansion",
          "version": "v1",
          "hashScope": true
        },
        "scope": ["tableId", "user", "tenant"],
        "clearOn": ["resetPreferences", "logout", "tenantChange"]
      },
      "security": {
        "eventExposureDefault": {
          "rowId": "hashed",
          "expandedKeys": "none"
        },
        "allowRawExposure": false
      },
      "deepLink": {
        "enabled": false,
        "queryParam": "expanded",
        "encoding": "csv",
        "keyFormat": "^[a-zA-Z0-9_-]{1,64}$",
        "maxKeys": 5,
        "maxLength": 256,
        "parsing": {
          "duplicateParams": "firstWins",
          "decodePasses": 1,
          "trimWhitespace": true,
          "sortKeysBeforeApply": true
        },
        "integrity": {
          "mode": "opaqueToken",
          "exchange": {
            "endpointId": "resolveExpandedKeysToken",
            "ttlMs": 300000,
            "request": {
              "token": "string",
              "tableId": "string"
            },
            "response": {
              "expandedRowKeys": "string[]"
            },
            "errors": ["expired", "invalid", "rate_limited"]
          }
        },
        "privacy": {
          "mode": "denyByDefault",
          "allowListTables": []
        }
      }
    }
  }
}
```

Nota de escopo por fase:

1. P0 usa contexto DSL `row/computed` (alinhado ao runtime atual);
2. `user/env` entram apenas em P1 via contexto injetado pelo host com contrato explícito;
3. `interaction.keyboard.profile` inicia em `disclosure` no P0; perfil `grid` entra com suíte dedicada no P1.

## 6.2 Regras de validação (fail-closed)

1. `enabled=true` exige `identity.requireStableIdField=true` e `table.idField` estável -> sem isso, desabilitar expansão + warning + diagnóstico estruturado.
2. `mode=controlled` -> runtime não muta `expandedRowKeys`; somente emite evento.
3. `allowMultiple=false` com `maxExpandedRows>1` -> coerção para `1`.
4. `lazyLoad.enabled=true` sem `actionId` -> bloqueio do lazy + diagnóstico.
5. `detail.source.mode` deve ser `inline`, `resource` ou `resourcePath`.
6. `detail.source.mode=inline` sem `detail.source.inlineSchema` -> inválido.
7. `detail.source.mode=resource` sem `detail.source.resource.kind/id/version` -> inválido.
8. `detail.source.mode=resourcePath` sem `detail.source.resourcePath.path/method` -> inválido.
9. `detail.source.mode=resourcePath` sem `detail.source.resourceAllowList` em ambiente restrito -> inválido.
10. `detail.rendering.strategy=registry` sem `detail.rendering.registryId` -> inválido.
11. nó presente no schema sem renderer registrado -> fail-closed (`EXPANSION_DETAIL_RENDERER_MISSING`).
12. `schemaContract.compat=semver` com versão incompatível -> fail-closed + hint de migração.
13. `detail.height.mode=dynamic` com virtualização ativa e política fixa -> expansão bloqueada no modo virtualizado.
14. `interaction.keyboard.profile=disclosure` com `arrowLeftRight=true` -> coerção para `false`.
15. `interaction.keyboard.profile=grid` sem semântica de grid habilitada -> fallback para `disclosure` + diagnóstico.
16. deep-link deve canonicalizar (`decodePasses=1`, `duplicateParams=firstWins`, trim + ordenação) antes de validar formato.
17. deep-link acima de `maxLength`/`maxKeys` -> parsing negado + limpeza segura (somente quando `deepLink.enabled=true` em P1).
18. deep-link fora de `keyFormat` -> parsing negado + diagnóstico (somente quando `deepLink.enabled=true` em P1).
19. `security.allowRawExposure=false` -> runtime bloqueia emissão `raw` em eventos/logs.
20. `persistence.enabled=true` sem `storageKeyStrategy.namespace/version` -> invalidar persistência + diagnóstico.
21. token opaco inválido/expirado -> deep-link rejeitado com diagnóstico (P1).

## 6.3 Contrato semântico do Detail Schema (AST)

1. `schemaContract.kind` define namespace estável (`praxis.detail.schema`).
2. `schemaContract.compat=semver` define política de compatibilidade.
3. `schemaContract.allowedNodes` limita nós permitidos do AST.
4. `schemaContract.sanitization=strict` força sanitização forte no runtime.
5. toda validação de AST deve ser executada por `SchemaContractValidator` (runtime e editor).

Exemplo de estrutura AST mínima suportada:

```json
{
  "layout": "tabs",
  "items": [
    {
      "type": "tab",
      "id": "summary",
      "label": "Resumo",
      "content": [{ "type": "card", "title": "Status", "fields": [{ "label": "Status", "value": "$data.status" }] }]
    },
    {
      "type": "tab",
      "id": "items",
      "label": "Itens",
      "content": [{ "type": "tableRef", "schemaId": "order-items-table-v1" }]
    }
  ]
}
```

## 6.4 Funcionamento da Feature em Modelo Schema-Driven UI

Pipeline de execução (runtime):

1. carregar `behavior.expansion` do JSON;
2. validar shape com schema fechado (sem propriedades órfãs) + regras fail-closed;
3. resolver fonte do schema de detalhe (`detail.source.mode=inline|resource|resourcePath`);
4. transformar AST em render tree via registry de renderers (`layout`, `tabs`, `card`, `formRef`, `tableRef`, `chartRef`, `richText`, `value`, `action`, `list`);
5. renderizar nós por componente Angular dinâmico, com bindings estritos de contexto (`row`, `computed`);
6. aplicar políticas de layout/altura por modo de render (não virtualizado vs virtualizado fixo);
7. aplicar lazy load/caching/retry/cancel e anunciar estado assíncrono com `aria-busy` + `aria-live`;
8. emitir `rowExpansionChange` redigido por política de exposição e publicar métricas.

Diretrizes de arquitetura Schema-Driven UI:

1. runtime e editor devem compartilhar o mesmo `SchemaContractValidator` para evitar drift;
2. renderização deve usar whitelist de tipos e contratos (`schemaContract.allowedNodes`), nunca execução arbitrária;
3. registry de renderers precisa ser extensível por token/injeção (enterprise plugin model) com fallback seguro;
4. DSL e parsing URL usam canonicalização determinística antes de validação para reduzir bypass;
5. feature flags por fase (`P0/P1/P2`) devem bloquear capacidades não suportadas no caminho virtualizado.

## 6.4.1 Princípio arquitetural (Praxis)

1. `detail` é sempre schema-driven (não há bifurcação de tipo em `schema|template`).
2. o que varia é a **fonte do schema** (`source.mode`) e a **estratégia de renderização** (`rendering.*`).
3. nós de composição (`card`, `formRef`, `tableRef`, `chartRef`, `richText`, `tabs`, `list`) são capacidades de plataforma, não exceções por caso.

Exemplo A (`source.mode=resource`, recomendado enterprise):

```json
{
  "detail": {
    "schemaContract": { "kind": "praxis.detail.schema", "version": "1.0.0", "compat": "semver" },
    "source": {
      "mode": "resource",
      "resource": { "kind": "ui-composition", "id": "customer-detail-v2", "version": "2.1.0" },
      "contextMap": { "customerId": "$row.id", "tenantId": "$ctx.tenantId" }
    },
    "rendering": { "strategy": "registry", "registryId": "praxis.detail.default" }
  }
}
```

Exemplo B (`source.mode=resourcePath`, integrado ao pipeline CRUD):

```json
{
  "detail": {
    "schemaContract": { "kind": "praxis.detail.schema", "version": "1.0.0", "compat": "semver" },
    "source": {
      "mode": "resourcePath",
      "resourcePath": {
        "path": "orders/{orderId}/detail-schema",
        "paramsMap": { "orderId": "$row.id" },
        "method": "GET"
      },
      "resourceAllowList": ["orders/*/detail-schema"]
    },
    "rendering": { "strategy": "registry", "registryId": "praxis.detail.default" }
  }
}
```

Exemplo C (`source.mode=inline`, fallback/local):

```json
{
  "detail": {
    "schemaContract": {
      "kind": "praxis.detail.schema",
      "version": "1.0.0",
      "compat": "semver",
      "allowedNodes": ["tabs", "tab", "card", "formRef", "tableRef", "chartRef", "richText"]
    },
    "source": {
      "mode": "inline",
      "inlineSchema": {
        "layout": "tabs",
        "items": [
          { "type": "tab", "id": "overview", "label": "Resumo", "content": [{ "type": "card", "title": "Conta" }] },
          { "type": "tab", "id": "edit", "label": "Formulário", "content": [{ "type": "formRef", "schemaId": "account-edit-v1" }] },
          { "type": "tab", "id": "history", "label": "Histórico", "content": [{ "type": "tableRef", "schemaId": "account-history-v1" }] }
        ]
      }
    },
    "rendering": { "strategy": "registry", "registryId": "praxis.detail.default", "hostLayout": "tabs" }
  }
}
```

## 6.5 Evolução de Contrato (Deep Dive)

## 6.5.1 Fronteiras de contrato que precisam evoluir

1. `TableBehaviorConfig` em `@praxisui/core` deve ganhar `expansion?: TableExpansionConfig` como capacidade nativa.
2. `createDefaultTableConfig()` deve incluir defaults explícitos de `behavior.expansion` com `enabled=false` (mudança aditiva, sem ativação implícita).
3. metadata pública do componente (`PRAXIS_TABLE_COMPONENT_METADATA`) deve declarar o novo evento `rowExpansionChange`.
4. JSON API da tabela deve publicar paths `behavior.expansion.*` com status (`Active/Partial/Schema-only`) por fase.
5. catálogo de capacidades AI/context pack deve incluir os novos paths para evitar recomendações inválidas.
6. painel/editor de comportamento deve refletir as mesmas coerções do runtime (single source of truth para validação).

## 6.5.2 Proposta de shape tipado em `@praxisui/core` (draft)

```ts
export interface TableBehaviorConfig {
  pagination?: PaginationConfig;
  sorting?: SortingConfig;
  filtering?: FilteringConfig;
  selection?: SelectionConfig;
  interaction?: InteractionConfig;
  loading?: LoadingConfig;
  emptyState?: EmptyStateConfig;
  virtualization?: VirtualizationConfig;
  resizing?: ResizingConfig;
  dragging?: DraggingConfig;
  localDataMode?: TableLocalDataModeConfig;
  expansion?: TableExpansionConfig; // NOVO
}

export interface TableExpansionConfig {
  enabled?: boolean;
  contractVersion?: '1.0.0';
  identity?: {
    rowKeySource?: 'table.idField';
    requireStableIdField?: boolean;
  };
  state?: {
    mode?: 'controlled' | 'uncontrolled';
    expandedRowKeys?: string[];
    emitChanges?: boolean;
    sourceOfTruth?: 'input' | 'runtime';
    onSetExpandedKeys?: 'emitOnly' | 'mutateInternal';
  };
  interaction?: {
    trigger?: 'icon' | 'row' | 'both';
    toggleOnRowClick?: boolean;
    keyboard?: {
      profile?: 'disclosure' | 'grid';
      enterSpace?: boolean;
      arrowLeftRight?: boolean;
    };
  };
  limits?: {
    allowMultiple?: boolean;
    maxExpandedRows?: number;
    onOverflow?: 'collapseOldest' | 'denyNew' | 'collapseAll';
  };
  collapseOn?: {
    sortChange?: boolean;
    pageChange?: boolean;
    filterChange?: boolean;
    dataRefresh?: boolean;
    groupChange?: boolean;
    columnVisibilityChange?: boolean;
    columnOrderChange?: boolean;
    columnResizeChange?: boolean;
    densityChange?: boolean;
    rowSelectionChange?: boolean;
    viewportDataWindowChange?: boolean;
  };
  rowExpandableWhen?: {
    expr?: string;
    onError?: 'deny' | 'allow';
    context?: Array<'row' | 'computed' | 'user' | 'env'>;
  };
  detail?: {
    schemaContract?: {
      kind?: 'praxis.detail.schema';
      version?: string;
      compat?: 'semver';
      allowedNodes?: Array<
        | 'layout'
        | 'stack'
        | 'tabs'
        | 'tab'
        | 'card'
        | 'value'
        | 'action'
        | 'list'
        | 'formRef'
        | 'tableRef'
        | 'chartRef'
        | 'richText'
        | 'templateRef'
      >;
      sanitization?: 'strict';
    };
    source?: {
      mode?: 'inline' | 'resource' | 'resourcePath';
      inlineSchema?: Record<string, unknown>;
      resource?: {
        kind?: 'ui-composition' | 'form-schema' | 'table-schema' | 'dashboard-schema';
        id?: string;
        version?: string;
      };
      resourcePath?: {
        path?: string;
        paramsMap?: Record<string, string>;
        method?: 'GET' | 'POST';
      };
      contextMap?: Record<string, string>;
      resourceAllowList?: string[];
      fallbackMode?: 'none' | 'inline' | 'resource';
    };
    rendering?: {
      strategy?: 'registry';
      registryId?: string;
      hostLayout?: 'auto' | 'stack' | 'tabs';
      fallbackNodePolicy?: 'failClosed' | 'renderPlaceholder';
    };
    height?: { mode?: 'fixed' | 'dynamic'; px?: number };
    lazyLoad?: {
      enabled?: boolean;
      actionId?: string;
      cache?: { enabled?: boolean; ttlMs?: number };
      retry?: { maxAttempts?: number };
      cancelOnCollapse?: boolean;
      dedupeByRowKey?: boolean;
    };
  };
  virtualization?: {
    policy?: 'fixed-height-only' | 'allow-dynamic-under-flag';
  };
  persistence?: {
    enabled?: boolean;
    storageKey?: string;
    storageKeyStrategy?: {
      namespace?: string;
      version?: string;
      hashScope?: boolean;
    };
    scope?: Array<'tableId' | 'componentInstance' | 'user' | 'tenant'>;
    clearOn?: Array<'resetPreferences' | 'logout' | 'tenantChange'>;
  };
  security?: {
    eventExposureDefault?: {
      rowId?: 'redacted' | 'hashed' | 'raw';
      expandedKeys?: 'none' | 'hashed' | 'raw';
    };
    allowRawExposure?: boolean;
  };
  deepLink?: {
    enabled?: boolean;
    queryParam?: string;
    encoding?: 'csv';
    keyFormat?: string;
    maxKeys?: number;
    maxLength?: number;
    parsing?: {
      duplicateParams?: 'firstWins' | 'reject';
      decodePasses?: 1;
      trimWhitespace?: boolean;
      sortKeysBeforeApply?: boolean;
    };
    integrity?: {
      mode?: 'opaqueToken';
      exchange?: {
        endpointId?: string;
        ttlMs?: number;
        errors?: Array<'expired' | 'invalid' | 'rate_limited'>;
      };
    };
    privacy?: {
      mode?: 'denyByDefault' | 'allowByDefault';
      allowListTables?: string[];
    };
  };
}
```

## 6.5.3 Semântica de versionamento (sem breaking change)

1. manter `meta.version='2.0.0'` no contrato raiz para preservar consumidores atuais.
2. versionar a capability nova em `behavior.expansion.contractVersion='1.0.0'`.
3. tratar ausência de `behavior.expansion` como feature desligada (`enabled=false`) e sem warnings de erro.
4. evoluções incompatíveis futuras da expansão devem subir `contractVersion` local (não o `meta.version` global).

## 6.5.4 Regras formais de precedência e compatibilidade

1. estado: `controlled input` > `deepLink válido` > `persistência` > vazio.
2. dados: expansão não altera precedência de `DataMode` já existente (`resourcePath > data > persistido`).
3. teclado: `behavior.interaction.keyboard` continua governando navegação global da tabela; `behavior.expansion.interaction.keyboard` governa toggle de expansão.
4. virtualização: `behavior.virtualization.strategy='dynamic'` não habilita detalhe dinâmico automaticamente; depende de `expansion.virtualization.policy` + fase/flag.
5. eventos: payload deve ser discriminado por exposição (`none/hashed/raw`) para impedir vazamento acidental em compile-time/runtime.

## 6.5.5 Validação e coerção obrigatórias (runtime + editor)

1. validação estrutural: rejeitar propriedades órfãs em `behavior.expansion` (schema fechado).
2. validação semântica: `enabled=true` exige `table.idField` estável e resolvível.
3. coerções determinísticas: conflitos (`allowMultiple=false` + `maxExpandedRows>1`) são normalizados.
4. canonicalização URL: parse/canonicalize/validate sempre nessa ordem.
5. política de segurança: `allowRawExposure=false` bloqueia emissão raw mesmo se consumidor tentar forçar.

## 6.5.6 Taxonomia de diagnósticos (padronizar códigos)

1. `EXPANSION_IDFIELD_REQUIRED`
2. `EXPANSION_KEYBOARD_PROFILE_FALLBACK`
3. `EXPANSION_VIEWPORT_POLICY_RENAMED`
4. `EXPANSION_DEEPLINK_DEFERRED_P1`
5. `EXPANSION_DEEPLINK_PARSE_REJECTED`
6. `EXPANSION_SCHEMA_CONTRACT_INVALID`
7. `EXPANSION_DETAIL_SOURCE_INVALID`
8. `EXPANSION_DETAIL_RESOURCE_INVALID`
9. `EXPANSION_DETAIL_RESOURCEPATH_INVALID`
10. `EXPANSION_DETAIL_RENDERER_MISSING`
11. `EXPANSION_DETAIL_LEGACY_TEMPLATE_MIGRATED`
12. `EXPANSION_PERSISTENCE_STRATEGY_INVALID`
13. `EXPANSION_VIRTUALIZATION_DYNAMIC_BLOCKED`

## 6.5.7 Matriz de migração de contrato (configs legadas)

| Condição legada | Transformação | Diagnóstico |
| --- | --- | --- |
| sem `behavior.expansion` | inserir defaults (`enabled=false`) | nenhum |
| `collapseOn.viewportChange` | renomear para `viewportDataWindowChange=false` | `EXPANSION_VIEWPORT_POLICY_RENAMED` |
| `keyboard.profile` ausente | default `disclosure` | nenhum |
| `profile=disclosure` + `arrowLeftRight=true` | coerção para `false` | `EXPANSION_KEYBOARD_PROFILE_FALLBACK` |
| `persistence.enabled=true` sem `storageKeyStrategy` | inserir default namespaced/versionado | `EXPANSION_PERSISTENCE_STRATEGY_INVALID` |
| legado `detail.type=template` + `templateId` | migrar para `source.mode=inline` + `inlineSchema.items=[{ type:'templateRef', id: templateId }]` | `EXPANSION_DETAIL_LEGACY_TEMPLATE_MIGRATED` |
| `detail.source.mode=resource` sem `resource.kind/id/version` | fail-closed | `EXPANSION_DETAIL_RESOURCE_INVALID` |
| `detail.source.mode=resourcePath` sem `resourcePath.path/method` | fail-closed | `EXPANSION_DETAIL_RESOURCEPATH_INVALID` |
| `detail.rendering.strategy=registry` sem `registryId` | fail-closed | `EXPANSION_DETAIL_SOURCE_INVALID` |
| sem `schemaContract.kind/version` | fail-closed | `EXPANSION_SCHEMA_CONTRACT_INVALID` |

## 6.5.8 Artefatos impactados (implementação obrigatória)

1. `projects/praxis-core/src/lib/models/table-config-v2.model.ts`
2. `projects/praxis-core/src/lib/models/table-config.model.ts`
3. `projects/praxis-table/src/lib/praxis-table.metadata.ts`
4. `projects/praxis-table/src/lib/praxis-table.json-api.md`
5. `projects/praxis-table/src/lib/ai/table-ai-capabilities.ts`
6. `projects/praxis-table/src/lib/ai/table-context-pack.ts`
7. `projects/praxis-table/src/lib/behavior-config-editor/behavior-config-editor.component.ts`

---

## 7. Precedência Oficial de Estado

## 7.1 Fonte da verdade (ordem)

1. `state.mode=controlled` + `expandedRowKeys` (input);
2. deep-link (se habilitado e válido, a partir do P1);
3. persistência local (se habilitada);
4. default vazio.

## 7.2 Mutabilidade por modo

| Modo | Runtime altera estado interno | Runtime altera input | Emite evento |
| --- | --- | --- | --- |
| `controlled` | Não | Não | Sim (quando `emitChanges=true`) |
| `uncontrolled` | Sim | N/A | Sim (quando `emitChanges=true`) |

---

## 8. Segurança e Privacidade

1. deep-link com IDs é opt-in e deny-by-default (feature entra no P1).
2. limitar quantidade e tamanho de payload em URL.
3. canonicalização obrigatória antes de validar (`decodePasses=1`, trim, dedupe por política e ordenação determinística).
4. parsing estrito e sanitização (sem aceitar chaves inválidas/orfãs).
5. rejeitar parâmetros duplicados fora da política (`duplicateParams`) com diagnóstico estruturado.
6. logs/eventos/telemetria não devem expor IDs brutos por default.
7. persistência deve respeitar política de limpeza por reset/logout/context switch.
8. persistência deve usar namespace/versionamento para evitar colisão cross-app/tenant/ambiente.
9. runtime deve aplicar redaction default (`raw` somente em modo explicitamente permitido).
10. bloquear state injection por URL com validação de formato e integridade (P1).
11. estratégia de integridade para URL em SPA: **token opaco server-side** (evitar HMAC no cliente, P1).
12. contrato de exchange deve mapear erros (`expired`, `invalid`, `rate_limited`) para diagnósticos e UX acessível (P1).

---

## 9. Arquitetura de Runtime

## 9.1 Estado interno mínimo

1. `expandedRowKeys: Set<string>`
2. `detailLoadingKeys: Set<string>`
3. `detailErrorByKey: Map<string, string>`
4. `detailDataByKey: Map<string, unknown>`
5. `expansionDiagnostics: Array<DiagnosticEntry>` (estruturado para editor/log).
6. `interactionPolicyResolver: InteractionPolicyResolver` (enforcement testável de precedência).
7. `schemaContractValidator: SchemaContractValidator` (shape no P0, AST completo no P1).
8. `securityDefaults: SecurityDefaults` (redaction/parsing/integrity).

## 9.2 Precedência de interação

1. trigger de expansão;
2. teclado conforme `interaction.keyboard.profile` (`disclosure` no P0, `grid` no P1);
3. row actions/menu;
4. seleção;
5. `rowClick`.

## 9.3 Pipeline de deep-link (obrigatório no P1)

Fluxo canônico:

1. parse bruto do query param;
2. canonicalização determinística (decode único, trim, dedupe por política, ordenação);
3. validação de formato/tamanho (`keyFormat`, `maxKeys`, `maxLength`);
4. exchange do token opaco (`endpointId`);
5. validação do payload retornado;
6. reconciliação com dataset atual;
7. aplicação do estado (ou diagnóstico fail-closed).

## 9.4 Custos de avaliação DSL

`rowExpandableWhen` deve usar cache por `rowKey` com invalidação quando:

1. row data muda;
2. dependências computadas mudam;
3. contexto de usuário/ambiente muda (quando habilitado pelo host no P1).

## 9.5 Evento canônico (auditoria + integração)

`rowExpansionChange` (obrigatório) com contrato discriminado por exposição:

```ts
type RowExpansionChangeBase = {
  tableId: string;
  trigger: 'icon' | 'row' | 'keyboard' | 'api' | 'restore';
  reasonCode: 'user' | 'policy' | 'restore' | 'api';
  expanded: boolean;
  persisted: boolean;
};

type RowExpansionChangeEvent =
  | (RowExpansionChangeBase & {
      rowIdExposure: 'redacted';
      rowIdRef: null;
      expandedKeysExposure: 'none';
      previousExpandedKeysRef: null;
      currentExpandedKeysRef: null;
    })
  | (RowExpansionChangeBase & {
      rowIdExposure: 'hashed';
      rowIdHash: string;
      expandedKeysExposure: 'hashed';
      previousExpandedKeysHash: string[] | null;
      currentExpandedKeysHash: string[] | null;
    })
  | (RowExpansionChangeBase & {
      rowIdExposure: 'raw';
      rowId: string;
      expandedKeysExposure: 'raw';
      previousExpandedKeys: string[] | null;
      currentExpandedKeys: string[] | null;
    });
```

---

## 10. Virtualização (governança por modo)

| Cenário | Status |
| --- | --- |
| Não virtualizado + `fixed` | Active |
| Não virtualizado + `dynamic` | Active |
| Virtualizado + `fixed` | Active |
| Virtualizado + `dynamic` | Partial/Blocked por política no P0 |

Regra de rollout: manter `dynamic` em virtualizado somente após P2 e sob feature flag.

Semântica obrigatória de viewport:

1. `collapseOn.viewportDataWindowChange` representa mudança estrutural da janela de dados (ex.: troca de dataset/página virtual), não eventos de scroll pixel-a-pixel;
2. default recomendado: `false` no preset enterprise para evitar colapso inesperado durante navegação.

Nota de complexidade (crítica):

1. existem dois caminhos de render independentes (`mat-table` e virtualizado);
2. P0A cobre somente não virtualizado;
3. P0B cobre virtualizado fixo, sem detail row de altura variável.

---

## 11. UX + A11y (WCAG 2.2 AA)

1. trigger com `aria-expanded`, `aria-controls`, `aria-label`;
2. teclado por perfil: `disclosure` (Enter/Space) e `grid` (setas + roving tabindex, P1);
3. foco retorna ao trigger após colapso;
4. mensagens `aria-live` para expand/collapse/bloqueio/erro;
5. lazy detail anuncia estado assíncrono com `aria-busy` e estado carregado/erro;
6. `prefers-reduced-motion` respeitado;
7. atender WCAG 2.2 AA para `Focus Not Obscured (Minimum)` e `Target Size (Minimum)` no trigger.

Chaves i18n mínimas:

1. `table.expansion.toggle.expand.ariaLabel`
2. `table.expansion.toggle.collapse.ariaLabel`
3. `table.expansion.status.expanded`
4. `table.expansion.status.collapsed`
5. `table.expansion.status.blockedPolicy`
6. `table.expansion.status.lazyLoadError`
7. `table.expansion.status.loading`
8. `table.expansion.status.loaded`

---

## 12. Editor + Governança JSON-Driven

## 12.1 Behavior editor

Adicionar seção `Expansion` cobrindo:

1. `enabled`, `state.mode`, `state.emitChanges`;
2. `identity.*` (dependência de `table.idField`);
3. `interaction.*` incluindo `keyboard.profile`;
4. `limits.*`;
5. `collapseOn.*` (com semântica explícita de `viewportDataWindowChange`);
6. `rowExpandableWhen.*`;
7. `detail.schemaContract/detail.source/detail.rendering/height/lazyLoad`;
8. `virtualization.policy`;
9. `persistence.*` incluindo `storageKeyStrategy.*`;
10. `deepLink.*` (incluindo privacy/limites/parsing).

## 12.2 JSON editor

1. P0: validação estrutural mínima + coerções documentadas;
2. P1: validação síncrona completa com `SchemaContractValidator` compartilhado (runtime/editor);
3. P1: diagnósticos estruturados (codes/severity/hints) e round-trip sem perda.

## 12.3 Presets corporativos recomendados

Preset `enterprise-safe` (recomendado):

1. `collapseOn.dataRefresh=true`;
2. `collapseOn.columnVisibilityChange=true`;
3. `collapseOn.columnOrderChange=true`;
4. `collapseOn.columnResizeChange=true`;
5. `collapseOn.densityChange=true`;
6. `collapseOn.viewportDataWindowChange=false`.

Pode ser relaxado por caso de uso, mas o default recomendado para produção é fail-safe.

## 12.4 AI metadata

Atualizar `table-ai-capabilities` e context pack com `behavior.expansion.*` para evitar recomendações inválidas.
Adicionar categorias de segurança/privacidade (`deepLink.keyFormat`, `deepLink.integrity`, `event exposure mode`).
Execução recomendada: entregar já em P0A (baixo risco técnico e alto ganho de governança).

---

## 13. Observabilidade Mínima (obrigatória já em P0/P1)

## P0

1. contadores: expand, collapse, blockedPolicy;
2. erros: lazyLoadError, parseError, validationError;
3. timing básico: tempo de lazy load.
4. contadores de segurança: `deepLinkRejected`, `urlInjectionBlocked` (quando deep-link habilitado no P1), `redactionApplied`.
5. adapter/sink explícito (evitar telemetria ad-hoc):
   - `TableMetricsAdapter` (DI token);
   - implementação default `NoopTableMetricsAdapter`;
   - contrato mínimo: `count(name, tags)`, `timing(name, ms, tags)`, `error(name, tags)`.
6. padrão de nomes/tags:
   - metric names: `praxis.table.expansion.*`
   - tags permitidas: `tableId`, `reasonCode`, `result`
   - proibição: `rowId` e `expandedKeys` em tags (evitar cardinalidade e vazamento).

## P1

1. reason codes agregados;
2. taxa de uso por `detail.source.mode` (`inline/resource/resourcePath`) e mix de nós renderizados;
3. taxa de restauração via storage/deep-link.

## P2

1. métricas avançadas de performance (scroll jitter/frame budget) e auto-height experimental.

---

## 14. Plano por Fases

## P0A (fundação executável)

1. contrato `behavior.expansion` em `@praxisui/core` (model + defaults + coercions base);
2. precondição de identidade (`identity.rowKeySource=table.idField`) com guard em editor/runtime;
3. runtime não virtualizado (expand/collapse);
4. evento canônico discriminado por política de exposição;
5. precedência de interação com `keyboard.profile=disclosure`;
6. A11y base (`aria-expanded`, `aria-live`, foco de retorno);
7. testes de estado/precedência;
8. atualização inicial de docs e AI metadata.

## P0B (paridade operacional)

1. runtime virtualizado em política `fixed-height-only` (sem detail dinâmico);
2. `collapseOn` essenciais para mutações estruturais (incluindo `viewportDataWindowChange`, sem colapso por scroll);
3. persistência + reset com `scope/clearOn` + `storageKeyStrategy`;
4. observabilidade mínima com `TableMetricsAdapter`;
5. testes de regressão em ambos os caminhos de render (`mat-table` e virtualizado).

## P1 (expansão avançada governada)

1. detail schema completo com subset seguro + `SchemaContractValidator` compartilhado (runtime/editor);
2. lazy load completo (retry/cache/cancel/dedupe/timeout);
3. deep-link seguro com privacy mode + anti-injection URL + canonicalização determinística;
4. perfil de teclado `grid` com semântica APG e suíte dedicada;
5. editor completo com diagnósticos estruturados;
6. contexto DSL opcional `user/env` via contrato host-provided;
7. testes de integração cruzada.

## P2

1. altura dinâmica em virtualização sob flag;
2. telemetria/performance avançada;
3. harness/e2e corporativo.

## 14.1 Migração de Configs Legadas (V4 -> V5)

Objetivo: evitar drift de comportamento no rollout e garantir coerção determinística.

1. `behavior.expansion` ausente -> inserir defaults V5 (`enabled=false`) sem ativar feature implicitamente.
2. `rowExpandableWhen.context` com `user/env` antes do P1 -> coerção para `["row","computed"]` + diagnóstico `EXPANSION_CONTEXT_DEFERRED_P1`.
3. `deepLink.enabled=true` antes do P1 -> feature desabilitada em runtime + diagnóstico `EXPANSION_DEEPLINK_DEFERRED_P1`.
4. `detail.schemaContract.kind/version` ausente ou inválido -> fail-closed + `enabled=false` para expansão de detalhe.
5. `collapseOn` parcial -> preencher flags ausentes com preset `enterprise-safe`.
6. `security.allowRawExposure` ausente -> default `false`.
7. `persistence.scope` ausente -> default mínimo `["tableId"]` e recomendação de upgrade para `["tableId","user","tenant"]`.
8. legado `collapseOn.viewportChange` -> migrar para `collapseOn.viewportDataWindowChange=false` + diagnóstico `EXPANSION_VIEWPORT_POLICY_RENAMED`.
9. `interaction.keyboard.profile` ausente -> default `disclosure`; `arrowLeftRight=true` legado vira `false` no P0.
10. `persistence.storageKeyStrategy` ausente -> default `{ namespace: "praxis.table.expansion", version: "v1", hashScope: true }`.

## 14.2 Owners e Dependências Externas (Host-Provided)

| Item | Fase | Owner primário | Dependência externa | Critério de pronto |
| --- | --- | --- | --- | --- |
| Contexto DSL `user/env` | P1 | App Host (frontend) | Provider de contexto assinado em contrato | Contexto injetado e testado em runtime/editor |
| Deep-link `opaqueToken.exchange` | P1 | Backend/API + App Host | Endpoint `resolveExpandedKeysToken` com TTL/erros | Contrato request/response/errors implementado e testado |
| `persistence.scope` completo (`user/tenant`) | P0B/P1 | App Host (auth/session) | Fonte confiável de `userId` e `tenantId` | Escopo aplicado sem vazamento cross-tenant |
| Sink de métricas (`TableMetricsAdapter`) | P0B | Plataforma/Observability | Adapter para stack corporativa (ex.: OTEL/DataDog) | Métricas mínimas coletadas sem PII |

## 14.3 Checklist de Implementação por Camada (AS-IS -> TO-BE)

1. `@praxisui/core`:
   - introduzir `behavior.expansion` no modelo tipado (`TableBehaviorConfig`) e em `createDefaultTableConfig()`;
   - garantir coerções e validações mínimas de P0 (`identity`, `keyboard.profile`, `virtualization.policy`).
2. `praxis-table` runtime:
   - implementar expansão em `mat-table` primeiro, sem virtualização dinâmica;
   - integrar estado expandido ao ciclo de `DataMode` e às transições `remote/local/empty`;
   - aplicar evento canônico discriminado por exposição e métricas `praxis.table.expansion.*`.
3. `praxis-table` virtualização:
   - habilitar apenas `fixed-height-only` em P0B;
   - bloquear expansão quando `detail.height.mode=dynamic` com virtualização ativa;
   - validar ausência de colapso em scroll normal (`viewportDataWindowChange=false`).
4. `praxis-table` editor:
   - adicionar seção `Expansion` com guards de pré-condição (`table.idField`, policies, deep-link parsing);
   - reusar `SchemaContractValidator` no editor para paridade runtime.
5. Docs e JSON API:
   - atualizar documentação da tabela dinâmica com `behavior.expansion.*` e exemplos remotos/locais;
   - resolver referência de conceito schema-driven no workspace (evitar link quebrado).
6. AI metadata/context pack:
   - incluir novos paths e bloqueios de segurança no `table-ai-capabilities`/context pack;
   - adicionar hints de recomendação para `viewportDataWindowChange` e exposição de evento.

---

## 15. Testes Corporativos

## 15.1 Contrato/estado

1. testes de `controlled` (runtime não muta input);
2. testes de `uncontrolled`;
3. precedência input > deep-link > storage (deep-link a partir do P1);
4. `enabled=true` sem `table.idField` estável -> fail-closed com diagnóstico.

## 15.2 Segurança

1. deep-link inválido/overflow -> fail-closed (P1);
2. chaves inexistentes/orfãs -> ignorar com diagnóstico;
3. privacidade deny-by-default -> sem espelhamento em URL (P1).
4. redaction default -> sem IDs `raw` em logs/eventos.
5. token opaco inválido/expirado -> estado URL rejeitado (P1).
6. parâmetros duplicados/múltiplos encodings -> canonicalização + rejeição conforme política (P1).

## 15.3 Contrato de schema

1. `schemaContract.kind` inválido -> fail-closed;
2. versão incompatível -> fail-closed + hint;
3. node não permitido em `allowedNodes` -> fail-closed;
4. sanitização estrita aplicada e testada.

## 15.4 Runtime e integração

1. expand/collapse e limites;
2. `collapseOn.*` completo;
3. não quebrar sorting/filter/pagination/selection/rowAction/DnD;
4. ambos caminhos de render válidos (`mat-table` e virtualizado fixo);
5. virtualização não colapsa em scroll normal quando `viewportDataWindowChange=false`.

## 15.5 A11y e performance

1. teclado/foco/aria-live/aria-busy;
2. WCAG 2.2 AA: `Focus Not Obscured (Minimum)` e `Target Size (Minimum)` no trigger;
3. cenários de alto volume com virtualização;
4. budget de scroll sem regressão perceptível.

---

## 16. Checklist de Go-Live

1. `behavior.expansion` alinhado em `@praxisui/core` + `praxis-table` (model/defaults/editor/json-api/AI metadata).
2. estado controlado/uncontrolled formalmente testado.
3. política de virtualização aplicada e documentada (`fixed-height-only` no P0).
4. persistência com `scope/clearOn` ativa e testada.
5. diagnósticos estruturados disponíveis para editor e logs.
6. observabilidade mínima ativa com adapter/sink explícito (não postergada para P2).
7. `InteractionPolicyResolver` com suíte de regressão.
8. **Bloqueador P0**: redaction default ativo e testado (sem emissão `raw` por padrão).
9. **Bloqueador P1**: anti URL state-injection ativo e testado quando `deepLink.enabled=true`.
10. **Bloqueador P1**: `SchemaContractValidator` completo (fail-closed + migration hints) no runtime e editor para `detail.source` + AST de composição.
11. **Bloqueador P0**: contrato discriminado de evento impede payload sensível fora de política.
12. **Bloqueador P0B**: ausência de colapso por scroll normal no modo virtualizado validada por teste.
13. suíte de testes unit/integration/a11y/perf verde.
14. teste automatizado garantindo: **snippet canônico da doc == defaults reais do runtime**.
15. migração V4 -> V5 executada com relatório de diagnósticos e taxa de coerção.
16. owners/dependências externas de P1 formalmente atribuídos (host + backend).

---

## 17. Prompt Final para ChatGPT

```text
Atue como Arquiteto Front-end Enterprise (Angular/React), revisor UX/UI senior (Nielsen), especialista WCAG 2.2 AA e reviewer de contratos JSON-driven.

Faça uma revisao CRITICA do plano de expandable rows abaixo.

Formato obrigatorio:
1) Veredito executivo (Go / Go com ressalvas / No-Go)
2) Matriz comparativa (Praxis vs AG Grid vs MUI vs TanStack vs Kendo)
3) Findings priorizados (High/Medium/Low)
4) Gaps de contrato JSON
5) Viabilidade JSON-driven + editor
6) Ajustes no contrato (antes/depois)
7) Ajustes de arquitetura/runtime (P0/P1/P2)
8) Plano de testes corporativo
9) Melhorias de documentação/API
10) Checklist final de aceite de produção

Critérios:
- Não aceite suposições frágeis.
- Aponte inconsistências entre planejado e executável.
- Traga recomendações acionáveis com trade-offs.
- Use referências oficiais e inclua links.

Plano para revisão:
[COLE AQUI O CONTEÚDO DE projects/praxis-table/docs/expandable-rows-enterprise-big-leagues-plan.md]
```

---

## 18. Referências Oficiais

1. AG Grid Master/Detail: https://www.ag-grid.com/angular-data-grid/master-detail/
2. AG Grid Detail Grids: https://www.ag-grid.com/javascript-data-grid/master-detail-grids/
3. AG Grid Row IDs: https://www.ag-grid.com/javascript-data-grid/row-ids/
4. MUI Master-detail: https://mui.com/x/react-data-grid/master-detail/
5. MUI DataGridPro API: https://mui.com/x/api/data-grid/data-grid-pro/
6. TanStack Expanding Guide: https://tanstack.com/table/latest/docs/guide/expanding
7. Kendo Angular Master-Detail: https://www.telerik.com/kendo-angular-ui/components/grid/master-detail
8. Kendo React Detail Rows: https://www.telerik.com/kendo-react-ui/components/grid/rows/detail
9. Angular Dynamic Forms (metadata-driven): https://angular.dev/guide/forms/dynamic-forms
10. Angular `NgComponentOutlet` (dynamic component rendering): https://angular.dev/api/common/NgComponentOutlet
11. Angular Security Best Practices (sanitization/trust boundaries): https://angular.dev/best-practices/security
12. Angular Router - Read Route State (query params): https://angular.dev/guide/routing/read-route-state
13. Angular CDK Scrolling (fixed-size strategy e limites de auto-size): https://raw.githubusercontent.com/angular/components/main/src/cdk/scrolling/scrolling.md
14. JSON Schema Object Constraints (`additionalProperties`): https://json-schema.org/understanding-json-schema/reference/object?highlight=additionalproperties
15. WAI-ARIA APG Disclosure Pattern: https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
16. WAI-ARIA APG Grid Pattern: https://www.w3.org/WAI/ARIA/apg/patterns/grid/
17. WCAG 2.2 Quick Reference: https://www.w3.org/WAI/WCAG22/quickref/
18. URLSearchParams duplicate handling (`getAll`): https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/getAll
