feat(storefront): Show recommended products on cart and checkout - #812
feat(storefront): Show recommended products on cart and checkout#812vitorrgg wants to merge 2 commits into
Conversation
The shelf the legacy checkout SPA already renders never shows anything:
it looks products up by `{terms: {_id}}` on `search/_els`, which returns
zero hits on the current index, so the section silently hides itself.
Recommendations now come from a core composable that reads the cart
(bridging `window.ecomCart` on `/app/` pages), fetches the co-purchase
graph with a `related` fallback, and looks the products up by `_id` on
`search/v1`, which does work.
Mounting happens from `vbeta-app`, lazily and only on the cart, checkout
and confirmation routes, so no theme or store file has to change. Stores
opt out with `window.propsCartRecommendations = false`, or tweak it by
assigning props to that same global.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Revisão adversarialEscopoO PR deveria: vitrine de recomendados (grafo → Comparei com o padrão estável de RequiredR1. Inversão core→tema: build de tema sem R2. R3. A guarda de regressão que motivou o PR não roda em CI R4. OptionalO1. Opt-out inconsistente: o CSS injetado derrota O2. Rota O3. Polling de 10s por O4. Fetch do grafo sem timeout/ O5. Lacunas na suíte: sem teste para (a) cache do grafo (2 chamadas ao mesmo produto → 1 fetch), (b) erro do grafo ( Nit
Eixos sem achadosAuth: só header público Veredito: APROVAR COM RESSALVASO núcleo está bem construído — guard de
Ficou sem verificar: os ~15 temas 🤖 Generated with Claude Code |
- Reset `isFetching` when the cart is emptied while requests are in flight, so theme skeletons bound to it can't spin forever - Timeout Graphs API requests after 5s to prevent late layout shift on checkout, and only send `X-Store-ID` header when the store ID global is set - Skip polling for legacy `window.ecomCart` outside the legacy app document - Respect themes explicitly opting the legacy showcase back in (`canRecommendItems: true`) before hiding it with CSS - Guard `window.propsCartRecommendations` against non-object values - Wire tests to CI: `test` script on the package, `jsdom` declared, and new coverage for Graphs cache dedup/retry, API error fallback, `minIds` short-circuit and the `isFetching` race Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ajustes da revisão aplicadosCommit 361e68f resolve os achados da revisão acima:
Pendente (fora do código deste PR)
🤖 Generated with Claude Code |
Problema
Hoje
/app/#/carte/app/#/checkoutnão mostram recomendação nenhuma — mas não é porque falta código. O SPA legado (@ecomplus/storefront-app) já renderiza<recommended-items>nas duas telas: está fixo noTheCart.htmle presente noEcCheckout.htmlcomcanRecommendItemsdefaulttrue. Conferi no bundle publicado (app-cart.jseapp-checkout.jsdo beta.228) e a vitrine está lá.O que quebra é o segundo passo da busca. O componente pega os IDs no grafo de co-compra e depois procura os produtos via
@ecomplus/search-engine, que monta{terms: {_id: [...]}}nosearch/_els. Esse filtro retorna zero hits no índice v3:Trocando
skupor_id, com o ID do mesmo produto, vemtotal: 0. Queryidsfunciona,term/termsem_idnão. Como o componente fazv-if="items.length", a seção inteira some sem erro no console. Isso derruba junto oBuyTogethere os favoritos da conta.O grafo em si está vivo e populado —
apx-graphs.e-com.plus/products/{id}/recommended.jsondevolve co-compra normalmente para a loja 1024.Solução
Recomendação passa a vir do core, sem depender do SPA legado:
use-cart-recommendations.ts— itens do carrinho → grafo (recommended, com fallbackrelated) →search/v1?_id=, que funciona. Ordena pela relevância do grafo, filtra indisponível/sem estoque e o que já está no carrinho, e cacheia as respostas do grafo em memória. Em/app/o carrinho é do SPA legado, então o composable detectawindow.ecomCarte assina o eventochange— leitura apenas, sem escrever na mesma chave de localStorage. Fora do/app/usa oshoppingCartreativo normal.CartRecommendations.vue— a vitrine, usando oProductCarddo próprio tema (~/components/ProductCard.vue), então sai com a identidade de cada loja. Passalist-namepara os eventosview_item_list/select_item.cart-recommendations.ts— monta o app Vue logo abaixo do#storefront-app, passando pelo~/pages/_vuedo tema para herdar$t,$money,ALink,AImge o que mais o tema registrar.vbeta-app.ts— desliga a vitrine morta do SPA (canRecommendItems: false+ CSS escondendo.recommended-items, para não duplicar se osearch-enginefor consertado um dia) e faz o import dinâmico da montagem apenas quando a rota bate, então o Vue não entra no bundle em rotas que não usam.Rollout: zero arquivo por loja
A montagem vive no
vbeta-app, que o core já injeta na página/app/. Nenhum tema e nenhuma loja precisa mudar arquivo — só subir a versão do@cloudcommerce/storefront. Isso vale para os 16 temasstore-*e as 10 lojas v3.Quem quiser sair:
window.propsCartRecommendations = false, e a vitrine legada fica intacta como está hoje. Quem quiser ajustar atribui props no mesmo global, por exemplo{ onRoutes: ['cart', 'confirmation'], limit: 8 }.Como foi testado
Unitário (6 testes,
npx vitest runempackages/storefront): carrinho vazio, busca por_idnosearch/v1, fallbackrecommended→related, exclusão do que já está no carrinho e de indisponíveis, seleção da origem por quantidade, e o gate de rota. O primeiro é a guarda de regressão que importa: a busca tem que sair como_id=nosearch/v1, nunca comoterms._idno_els.Navegador (Puppeteer, loja demo 1011, contra uma worktree limpa do
ecomplus/storenofc2445e— sem nenhum arquivo de tema modificado, o que prova o rollout de custo zero):#/cart#/checkout#/order/...#/cartcom opt-outUma única chamada
search/v1?_id=...&limit=24por render. Como a loja demo não tem histórico de compras, o grafo dela volta vazio — interceptei só a resposta do grafo e deixei a busca e a renderização reais.Ressalvas
#/cartainda saem 3 chamadas ao grafo: 2 nossas e 1 do SPA legado. OcanRecommendItemssó existe noEcCheckout; no carrinho o<recommended-items>está fixo noTheCart.html, então dá para esconder por CSS mas não para impedir o fetch. É uma requisição cacheada, e some de vez quando essa parte do storefront-app sair do ar.vitest.config.tsentrou aqui porque o teste não roda sem ele. Ele nasceu em outra frente (lista de presentes); se for entrar por outro PR, é só remover deste.packages/storefrontnão tem scripttest, então estes testes ainda não rodam nopnpm test. Fora do escopo deste PR por mexer nopackage.json.onRoutespadrão incluicheckout. No primeiro passo (só o campo de e-mail) a vitrine sobe bastante na página; com o formulário cheio ela cai abaixo da dobra. Se preferir jogar seguro na conversão,['cart', 'confirmation']resolve.🤖 Generated with Claude Code