teqo/docs/plans/ops69-admin-branco-importmap-s3-impl.md
Francisco Solla 7162c65e37
All checks were successful
CI (PR) / checks (pull_request) Successful in 8m36s
PR Ready + auto-merge / ready-automerge (pull_request) Successful in 8m49s
Main merge flips claimed issues to done + in-prod / issue-done (pull_request) Successful in 13s
Promote plan-issue Related on main merge / plan-issue-ready (pull_request) Has been skipped
ops(OPS69): regenera importMap com o S3ClientUploadHandler (admin abria em branco em prod)
O importMap commitado ficou sem a entrada @payloadcms/storage-s3/client#S3ClientUploadHandler
desde o OPS52 (dabc00f4 removeu o handler do blob sem adicionar o do S3, porque o
generate:importmap rodou sem as envs S3_*). Com o plugin s3Storage condicional, sem as
envs o importMap sai consistente (dev/test) mas em produção (envs ativas) o admin falhava
ao montar a view — getFromImportMap: PayloadComponent not found in importMap — login nunca
renderizava. Fix: regenerar com as 4 S3_* dummy inline (loadEnvConfig do @next/env não
sobrescreve envs do processo; iterateConfig não toca DB) — diff de exatamente +2 linhas.
Smoke headless com S3 ativo: /admin/login renderiza, zero erros de console. Gates verdes
(tsc/lint/format/knip/cycles/unit+int 791/build). Sem migration/access/UI. Docs: nota no
AGENTS.md (generate:importmap precisa das S3_* enquanto o plugin for condicional) +
changelog. Guard de CI registrado como OPS70 (#87, depends #77).
2026-08-19 12:45:22 -03:00

6.6 KiB
Raw Blame History

Impl: Admin do Payload abre em branco em produção (importMap sem o handler do storage S3)

Status: aprovado Atualizado em: 2026-08-19 Issue: #77 Intenção: docs/plans/ops69-admin-branco-importmap-s3.md Appetite restante: herdado (~0,25–0,5 dia eng)

Leitura da intenção

  • Outcome: o /admin renderiza o formulário de login em produção; o importMap commitado volta a ser consistente com o config que as envs de produção ativam; dev/test sem S3_* seguem com storage local.
  • O que NÃO negociar: não mexer no contrato /api/media/file/..., não mudar o storage (Garage S3), não mudar o comportamento do plugin em dev/test, não tocar em nada além do fix mínimo do P0.
  • O que reavaliar: a hipótese "regenerar com envs S3 + diff só de storage" — verificado em evidência da intenção (reprodução local); a mecânica do payload generate:importmap foi confirmada no código (gera sem tocar DB; plugin só registra o handler quando ativo; env inline no processo vence os env-files via loadEnvConfig do @next/env).

Abordagem recomendada

flowchart LR
  A[generate:importmap com S3_* dummy inline] --> B[Diff: só +2 linhas do S3ClientUploadHandler]
  B --> C[Gates locais: tsc/lint/format/knip/cycles/unit+int/build]
  C --> D[Smoke: dev local com S3_* dummy → /admin renderiza]
  D --> E[changelog + linha AGENTS.md + item sucessor do guard]
  E --> F[PR em main → CI verde → deploy automático]

Opções consideradas: A | B | C | D

Recomendação: A — regenerar o importMap com as 4 envs S3_* (valores dummy inline) e commitar o diff, porque é o fix mínimo que elimina exatamente a divergência dev/prod: o generate:importmap walka o config via iterateConfig (sem DB), o plugin s3Storage registra o ClientUploadHandler da collection media só quando mediaStorage.enabled, e o CLI carrega env-files com @next/env sem sobrescrever envs já no processo — valores dummy inline garantem o enable sem depender de .env.local/secrets.

Alternativas rejeitadas:

  • B — editar importMap.js à mão: arquivo gerado; o próximo pnpm generate:importmap apaga a edição e a divergência volta sem aviso; nenhuma verificação de consistência.
  • C — tornar o s3Storage incondicional (sempre ativo, inclusive sem envs): quebra dev/test (anti-goal da intenção — storage local sem S3_*), exigiria Garage local em todo dev e muda o aceite OPS52.
  • D — guard de CI embutido neste item (rodar generate:importmap com envs dummy no gate e falhar se o arquivo commitado divergir): é a resposta certa para a classe de bug, mas a intenção cortou explicitamente melhorias de build/deploy do fix mínimo (questão em aberto → "C como item sucessor"). Registra-se como Issue sucessora no fechamento (depends em #77).

Componentes / mudanças

  • src/app/(payload)/admin/importMap.js (gerado, commitado): ganha 2 linhas — import + entrada @payloadcms/storage-s3/client#S3ClientUploadHandler (export confirmado em node_modules/@payloadcms/storage-s3/dist/exports/client.js). Nenhuma outra entrada muda.
  • package.json / scripts: nada muda; usa-se o pnpm generate:importmap existente com envs inline.
  • AGENTS.md: uma linha na checklist de verificação local — generate:importmap deve rodar com as envs S3_* setadas (dummy ok) enquanto o plugin de storage for condicional; sem elas o importMap fica órfão do handler (classe OPS69).
  • Migration: sem migration. Access / Consent: não se aplica. UI: Impeccable A — sem UI nova.

Guardas de consistência da regeneração

Antes de commitar: git diff do importMap.js deve conter exclusivamente as 2 linhas do storage S3. Se aparecer qualquer outra entrada (drift de componentes admin desde a última regen, a638a8ac), parar e investigar: se o drift é real (componente admin novo sem regen posterior), incluí-lo é consistência correta, mas exige conferir o commit que o introduziu; nunca commitar um diff não compreendido.

Fases verificáveis

  1. Regeneração — S3_BUCKET=teqo-media S3_ENDPOINT=http://localhost:3900 S3_ACCESS_KEY_ID=test S3_SECRET_ACCESS_KEY=test pnpm generate:importmap; diff restrito às 2 linhas do S3. (Quota: minutos)
  2. Verificação de consistência + smoke — git grep do handler no importMap; pnpm dev local com as mesmas envs dummy: /admin/login renderiza o formulário (o repro da intenção documenta que com S3 ativas + importMap órfão o admin fica branco; com o handler presente, renderiza). O e2e admin.e2e.spec.ts roda sem S3_* (CI) e continua passando — a entrada extra fica inerte.
  3. Gates — tsc --noEmit, pnpm lint, pnpm format:check, pnpm exec knip, pnpm check:cycles, pnpm test, pnpm build (DB local). E2e: CI (PR).
  4. Fechamento — docs/changelog/2026-08-19-ops69.md + pnpm changelog:build; linha no AGENTS.md; registrar Issue sucessora do guard via capture-review-debts (depends #77).

Rabbit holes / Não escopo (engenharia)

  • Regenerar e ver drift — acima: diff não compreendido = parada obrigatória.
  • "Aproveita e roda o guard no CI já" — item sucessor, não este P0.
  • Perseguir o erro de beacon do Cloudflare — ruído de console, fora de escopo (já cortado na intenção).
  • Testar upload real contra Garage local no dev — o smoke só precisa que a view renderize; o fluxo de upload S3 já é coberto pelo OPS52 em produção.

Riscos e mitigação

  • Regeneração traz drift além do S3 (componente admin adicionado desde a638a8ac sem regen): parar, conferir o commit que introduziu o componente, incluir só se for consistência legítima.
  • Alguém regenera sem envs no futuro e reintroduz o bug: mitigado pela linha do AGENTS.md + Issue sucessora do guard de CI (que falha o gate na divergência).
  • Deploy em produção com o importMap novo: verificação pós-deploy pelo runbook docs/ops/teqo-1313-deploy.md — /admin abre o login e o log do container não re-emite getFromImportMap: PayloadComponent not found in importMap para @payloadcms/storage-s3/client#S3ClientUploadHandler.

Aceite de engenharia

  • Aceite de produto da intenção ainda coberto (admin renderiza em prod; upload S3 intacto; dev/test sem S3_* intactos)
  • Invariantes AGENTS/engineering-standards (nenhum schema/access/UI tocado; importMap é arquivo gerado e commitado)
  • Diff do importMap restrito às 2 linhas do handler S3, verificado e compreendido
  • Gates verdes (tsc/lint/format/knip/cycles/unit+int/build; e2e no CI)
  • Changelog + AGENTS.md + Issue sucessora do guard registrada