# ADR-026 — Media + Gallery: fluxo único de ingestão/digestão e `<picture>`

| Campo | Valor |
|---|---|
| **Status** | Aceito (2026-10-20) |
| **Data** | 2026-10-14 |
| **Plano de origem** | `wiki/plan/19-media-gallery.md` |
| **Relacionadas** | ADR-009, ADR-017, ADR-015, ADR-011, ADR-022 |

## Contexto

Imagens são o **ativo mais pesado** do portal. O `v6-hardening` resolveu isso com um **middleware
que reescrevia `<img>`→`<picture>`** no HTML — funcional, mas frágil (regex em HTML, acoplado ao
layout). O `sucesu-cms` (`V2/Services/Media` + `V2/Services/Gallery`) e a **Concordia antiga**
(`cms/services/Media`) conceberam um modelo melhor: um **fluxo único e centralizado** onde o
`MediaInputer` é a **porta de entrada**, otimizadores **digerem** o arquivo, o resultado vive num
**catálogo de mídias** (Gallery) e a ponta é uma **tag `<picture>` altamente otimizada**. A
Concordia acrescenta **AVIF** (libvips), **width/height** em metadata (CLS zero) e **PDF/A**.

## Decisão

1. **Dois services** (ADR-015):
   - **`Media`** — headless: ingestão, digestão, otimização e **storage em S3** (ADR-030) + **componentes** de uso.
   - **`Gallery`** — catálogo/biblioteca de mídias + `GalleryConfig` + **`gallery-picker`**.
2. **Porta de entrada única:** `MediaInputer` roteia por **MIME detectado no servidor** (finfo,
   nunca o do cliente) para o otimizador correto. **Nada é servido bruto.**
3. **Digestão extrema:** variantes **AVIF + WebP** nas larguras `[360, 720, 1200, 1920, 2560]`
   (configurável), quality ~65–75; **SVG** é pulado (vetorial); imagem menor que a menor largura →
   **1 variante na largura original** (chave `original`). **Width/height originais** em
   `metadata` (jsonb) → **CLS zero**.
4. **Catálogo (Gallery):** tabela `gallery_media` (`owner_module`, `is_shared`, `file_path`,
   `mime_type`, `file_size`, `alt_text`, `description`, `variants` jsonb, `metadata` jsonb,
   `deleted_at`), índices parciais; `GalleryConfig` (allowedMimeTypes/maxFileSize/allowShared);
   arquivar/restaurar (soft delete — **não apaga o físico**, retenção).
5. **Render:** componente **`<x-service-media::picture>`** — `<picture>` com
   `<source type="image/avif">` + `<source type="image/webp">` + `<img>` fallback com
   `width`/`height`, `decoding="async"`, `loading="lazy"` e `fetchpriority="high"` no LCP.
   **Abandonar o middleware de reescrita de HTML** do v6.
6. **Nativo:** **Flysystem** do Laravel (sem `StorageService` próprio), disco **`s3`**
   (`league/flysystem-aws-s3-v3` — permitido, ADR-002; backend **RustFS** ↔ AWS S3 por `.env`, ADR-030);
   **filas** (Valkey) para
   digestão; **jsonb** (ADR-017). Encoder: **Imagick** (já no plano 04, com AVIF); **libvips**
   **opcional** (padrão Concordia) atrás do mesmo contrato para "extrema otimização".
7. **Acessibilidade:** **`alt` obrigatório** (eMAG/WCAG — ADR-011); nome de arquivo **slugificado**.
8. **Agnóstico:** `owner_module` é **token** (o service não importa módulos). O Gallery expõe
   **superfície administrativa** (rotas sob `/cms` + componente), nunca rotas públicas
   (refinamento do ADR-015).
9. **Preservação/segurança (futuro):** PDF → **PDF/A + linearização** (plano 17); scan de malware e
   **crypto-shredding** de mídia sensível (ADR-020/019).

## Justificativa (alternativas descartadas)

- **Middleware reescrevendo HTML** (v6) — descartada: frágil e acoplado ao layout.
- **`spatie/laravel-media-library`** — descartada: dependência; o fluxo cabe em services nativos.
- **`StorageService` próprio** (sucesu) — descartada: o **Flysystem** do Laravel já é agnóstico.
- **GD puro** — descartada: sem AVIF de qualidade; **Imagick/libvips** entregam "extrema otimização".

## Consequências

**Positivas**
- Um fluxo único de entrada→digestão→`<picture>`; imagens leves e sem CLS; reuso por todos os módulos.
- Sem reescrever HTML; nativo (Flysystem/filas/jsonb).

**Custos / negativas**
- Exige encoder de imagem (Imagick/libvips) e fila; digestão é assíncrona (status).
- Migração/backfill do acervo precisa de comando dedicado (idempotente).

## Exemplo mínimo (comparativo)

```php
// ❌ middleware reescrevendo HTML (v6) — frágil
// regex em <img> → <picture> na resposta

// ✅ porta única + digestão + catálogo + componente
$media = app(MediaInputer::class)->ingest($file, 'gallery/noticias');   // AVIF/WebP + metadata
$gallery->upload($file, new GalleryConfig('noticias', ['image/*']), alt: 'Fachada da prefeitura');
// render:
//   <x-service-media::picture :media="$media" :alt="$media->alt_text" />
//   → <picture><source type="image/avif" …><source type="image/webp" …>
//     <img width height decoding="async" loading="lazy"></picture>
```

## Emenda — upload em lote (F5.I4)

- **Não** existe multipart de 20–50 arquivos: a borda/PHP têm teto **por requisição** (110 MiB) e
  `max_file_uploads`; o lote é **N requisições de 1 arquivo** com `batch_id`, enviadas pelo cliente
  com concorrência 3.
- `media.media_batches` guarda dono, `expected_files`, `max_files=50`, `max_bytes=250 MiB`,
  `expires_at=24h` e contadores; a reserva é atômica (advisory lock do usuário + row lock do lote)
  **antes** de publicar `staging/`.
- Cota por usuário/dia: **2 GiB e 500 arquivos**; rate `media-upload` 120/min. A borda continua
  110 MiB (maior arquivo **único**); o total é do lote/quota, não do corpo HTTP.
- A grade exige **alt por imagem**; o commit do Gallery cataloga só uploads `concluido`, é
  idempotente por `path`+`module` e reporta falhas parciais (não derruba o lote).
- Prova: `MediaBatchTest` (inclui 50 arquivos), `GalleryBatchTest` e e2e `15-cms-galeria-lote`.

## Verificação

- Upload gera variantes AVIF+WebP nas larguras ≤ original; `<img>` tem `width`/`height`.
- `<x-service-media::picture>` sem variantes cai no `<img>` original; SVG não gera variante.
- `alt` vazio bloqueia publicação (a11y); arquivar não apaga o físico.
- Módulo usa o service **sem** importar outro módulo; nenhum HTML é reescrito por middleware.

## Referências

- `sucesu-cms/V2/Services/{Media,Gallery}/`; `cms/services/Media/`,
  `cms/specs/1_persistencia_de_dados/9_processamento_media_seo.md`, `cms/wiki/dev/media-srcset.md`;
  `v6-hardening/wiki/infra-update/webp-middleware-l5.md`.
