diff --git a/docs/TECH-DEBT.md b/docs/TECH-DEBT.md index 67e30c3b..a564e41f 100644 --- a/docs/TECH-DEBT.md +++ b/docs/TECH-DEBT.md @@ -27,7 +27,7 @@ Ledger of known engineering debt. Items large enough to be scheduled work become | Access checks re-read the actor per Local API call: `getFreshCampaignUser` caches in `req.context`, but a `payload.find({ user, overrideAccess: false })` that passes no `req` gets a fresh context, so one `/campanha/municipios` render issues ~5 identical `campaignUser` reads on top of the layout's own pair | `src/utilities/access/shared.ts` + every `overrideAccess: false` caller | perf (round-trip fan-out) | low since the `gru1` move (~2 ms each; it was ~120 ms each while functions ran in `iad1`) | M | P2 | open — measured 2026-07-26. A React `cache()` layer would dedupe across requests, but it changes actor freshness inside server actions that mutate roles (`reloadUnrestrictedActor`), so it needs its own delivery with pins | | | `authenticateCampaignToken` reads the actor twice: Payload's JWT strategy already fetches the user document, then `findByID(depth: 1)` re-reads it — the second read's only unique contribution is populating `avatar` for the sidebar | `src/utilities/campaignAuth.ts` | perf (a round trip in the layout, so on every page) | low | S | P2 | open — dropping it needs the avatar resolved another way, plus pins on `removePrivateAuthFields` and on role-downgrade freshness | RS+ | | `LeadershipStateDeputyRelationCell` (B31) keeps its own Popover, its own `ResizeObserver` collapse math and a 16 px "Ver mais…", while the sibling column in the same table now uses the shared `MunicipalityPortfolioCell` (always-mounted on `pointer-fine`, Drawer on `pointer-coarse`, 24 px targets, ARIA combobox). Two chip editors, one table, different keyboard and touch contracts | `src/components/campaign/leadership/LeadershipStateDeputyRelationCell.tsx` vs `src/components/campaign/shared/MunicipalityPortfolioCell.tsx` | duplication + a11y drift | low functionally, medium for consistency — the dobradinha chips are the only ones in the table a thumb cannot edit safely | M | P2 | open 2026-07-27 (B34), **trigger fired** — the reuse review measured ~105 line-for-line duplicated lines of `ResizeObserver` collapse math between the two cells (constants, the first-callback guard, the `lastVisibleTop + 1` filter, the trailing-width loop, the `expandToggle`), including one verbatim comment. Scheduled as **B34+ F2** (`useChipRowOverflow`); the duplication predates B34 (it was born between `AdvisorMunicipalityCell` and B31), which is why it is an extraction and not a regression. B37 still does not by itself justify a generic abstraction — it plugs the shared cell into a third relation | B34 | -| B34 shipped a **measured** First Load JS regression: `/campanha/liderancas` 246 → **304 kB** and `/campanha/assessores` 253 → **295 kB** (Next's minified figure, two full builds against a stashed clean `main`). Assessores already imported the static Bahia catalog, so its +42 kB is the **cell** (Drawer, links, combobox contract, measuring machinery) and only the remaining ~16 kB on lideranças is the catalog reaching a route that lacked it — the same cost B14 already named. The two fixes **oppose each other**: pulling the catalog out of the browser fattens the payload of every chip toggle, shrinking the payload to `{id,slug}` keeps the bundle | `src/lib/municipalityPortfolio.ts`, `src/utilities/municipalityPortfolioIndex.ts`, `src/components/campaign/shared/MunicipalityPortfolioCell.tsx` | perf (bundle + per-render payload) | medium — two of the most-used staff lists, on phones over 4G | M | P2 | open 2026-07-27 (B34) — direction recommended in the fill-in (payload-minimal, since staff edit far more than they navigate), but it trades the DB's `region` for the snapshot-tested catalog's, so it needs an explicit call. A third, orthogonal lever: load the Drawer behind `next/dynamic` on first touch — it only opens on `pointer-coarse` | B34+ | +| B34 shipped a **measured** First Load JS regression: `/campanha/liderancas` 246 → **304 kB** and `/campanha/assessores` 253 → **295 kB** (Next's minified figure, two full builds against a stashed clean `main`). Assessores already imported the static Bahia catalog, so its +42 kB is the **cell** (Drawer, links, combobox contract, measuring machinery) and only the remaining ~16 kB on lideranças is the catalog reaching a route that lacked it — the same cost B14 already named. The two fixes **oppose each other**: pulling the catalog out of the browser fattens the payload of every chip toggle, shrinking the payload to `{id,slug}` keeps the bundle | `src/lib/municipalityPortfolio.ts`, `src/utilities/municipalityPortfolioIndex.ts`, `src/components/campaign/shared/MunicipalityPortfolioCell.tsx` | perf (bundle + per-render payload) | medium — two of the most-used staff lists, on phones over 4G | M | P2 | open 2026-07-27 (B34) — direction recommended in the fill-in (payload-minimal, since staff edit far more than they navigate), but it trades the DB's `region` for the snapshot-tested catalog's, so it needs an explicit call. A third, orthogonal lever: load the Drawer behind `next/dynamic` on first touch — it only opens on `pointer-coarse`. **Sibling, not duplicate:** B33+ registers the same catalog in `/campanha/municipios`'s bundle, but by accident (a predicate dragging the module) rather than by need, so the two fixes differ — pay them in one pass if at all, since the measurement bench is shared | B34+ | | `AdvisorsTable` mixes an edit _mode_ (name/e-mail/phone behind an "Editar nome e contato" toggle) with a column that is always live (municípios). The toggle survives because those are text fields with no chip readout, but the row now speaks two interaction grammars at once | `src/components/campaign/advisor/AdvisorsTable.tsx` | UX inconsistency | low | M | P3 | open 2026-07-27 (B34) — resolve by giving the text fields the same edit-in-place treatment, not by re-hiding the chips | B34 | ## Smell Inventory diff --git a/docs/plans/escala-dry-pos-b34.md b/docs/plans/escala-dry-pos-b34.md index cee4d05a..e3b140db 100644 --- a/docs/plans/escala-dry-pos-b34.md +++ b/docs/plans/escala-dry-pos-b34.md @@ -36,6 +36,8 @@ A decomposição importa e ninguém deve re-derivá-la: `/campanha/assessores` * O staff edita muito e navega pouco, o que favorece **(b)** — o bundle é pago uma vez e fica em cache imutável, o payload é pago a cada toggle. A ressalva registrada é honesta: `buildMunicipalityPortfolioChips` hoje prefere de propósito o `region` **do banco** à resolução por slug do catálogo, "para os chips colaparem mesmo com divergência de alias entre ambientes". Essa justificativa é fraca — `municipalityCatalog` é snapshot-testado contra o catálogo semeado (`tests/fixtures/municipality-catalog.snapshot.json`), que é precisamente o guard que transforma divergência em teste vermelho — mas foi uma decisão deliberada, então a troca precisa de uma chamada explícita, não de um corte silencioso. +**Parente, não duplicata — o rebase em `main` trouxe isto à vista:** o **B33+** já registra que `/campanha/municipios` embarca ~10 kB gzip do mesmo catálogo, e o gatilho de lá já disparou duas vezes (B41 ✓, B42 ✓) sem ninguém pagar. Os dois são a mesma classe de problema com **causas opostas**, e a correção não é a mesma: lá o catálogo entra por **acidente** (um `isMunicipalitySlug` em `municipalityListUrl.ts` arrasta o módulo inteiro para três componentes de filtro que só queriam um predicado), e o conserto é mover o predicado para um módulo client-safe; aqui ele entra **de propósito**, porque a célula precisa mesmo de nome/região/ZE, e o conserto é escolher quem carrega o dado — bundle ou payload. Quem atacar um não fecha o outro; quem atacar os dois deve fazê-lo na mesma passada, porque a medição de First Load JS das três rotas é a mesma bancada. + Um terceiro lance, **ortogonal e cumulativo**: carregar o `Drawer` por `next/dynamic` atrás do primeiro toque. Ele só abre em `pointer-coarse`, e uma gaveta que já anima pode absorver o import. Recupera parte dos +42 kB que atingem as duas rotas. ## F2 — `useChipRowOverflow`: o gatilho disparou e a máquina foi copiada