fix(storefront): Show kit contents and pick variations on product page - #799
Conversation
Kit products (`kit_composition`) were rendered like plain products: the composition was never listed and kit items with variations went to cart without a selected SKU. - `use-product-details`: expose `isKit`, `kitComposition` and `selectKitVariation`, load kit items on mount and require a variation per composition slot before buying; shipping is now calculated with the kit items instead of the (weightless) kit product - `use-product-card`: fetch `variations` for kit items, honor a fixed `kit_composition[].variation_id`, cap kit quantity by the least available item (was taking the max) and set `kit_product` before adding to cart, otherwise a matching standalone item on cart was merged into the kit - new `KitComposition.vue` shared component listing each item with picture, link and quantity, with a `variations` slot for theme SKU selectors Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Splits the kit branch into `matchKitItem`, `sumToKitComposition` and `parseKitCartItems`, dropping `loadToCart` back under the complexity threshold flagged by CodeFactor. No behavior change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
leomp12
left a comment
There was a problem hiding this comment.
Boa PR — a direção está certa e três das quatro correções eu consegui confirmar contra dado real, não só lendo o código:
- A inversão
max→minno estoque do kit é a correção mais valiosa daqui. Simulei os dois algoritmos contra a API da tia sônia (loja 1024, 19 kits): o "Kit Amor que nutre" anunciava 289 unidades e o mínimo real dos componentes é 15; o "Kit com 5 Granolas" anunciava 726 contra 96 reais. Estávamos vendendo kit que não existe. kit_productantes doaddCartItemestá certo e o motivo é exatamente o descrito:add-cart-item.ts:29só entra no laço de merge quando!newItem.kit_product, então gravar depois realmente fundia o componente com o item avulso que já estava no carrinho.shippedItemspela composição encaixa direitinho na peça que já existe —use-shipping-calculator.ts:169-184já sabe buscar peso e dimensão por_ide aplicar a variação porvariation_id, então o cálculo passa a usar o componente real sem precisar de nada novo do outro lado.- A extração do commit 2 deixou
loadToCartlegível de verdade. E o básico está conferido:AImg/ALink/Skeletonsão globais registrados empages/_vue.ts:3-6,i19selectVariationei19outOfStockexistem empackages/i18n, evariation_iddentro dekit_product.compositionestá previsto no schema (packages/api/types/carts.d.ts:207).
O que me preocupa está quase todo concentrado nos quatro campos novos do kitItemFields.
🔴 Bloqueante — o gate de visible derruba um kit que está à venda hoje na tia sônia
use-product-card.ts:64 adiciona visible ao kitItemFields e use-product-card.ts:108 passa a recusar o item:
if (!kitItem?.available || kitItem.visible === false) return null;Fui atrás do dado na loja 1024:
KIT PIC5533 "Kit Cookies Chips + Cartela de Adesivos" /kit-cookies-chips
visible: true available: true R$ 26,90 estoque para 40 kits
└─ componente RV0078 "Cartela de Adesivos – Cookies Chips"
visible: FALSE available: true quantity: 219
É o padrão clássico: a cartela de adesivos é um brinde de R$2,90 que só existe dentro do kit, então está escondida do catálogo de propósito. Com essa PR o matchKitItem devolve null, o parseKitCartItems aborta, e o cliente que clicar em comprar recebe só o isFailedToCart genérico — sem nenhuma pista do que houve. Antes funcionava.
A tia sônia agrava o caso porque o ProductDetails.vue dela usa useProductCard direto (ProductDetails.vue:245), não o useProductDetails — ou seja, ela nem ganha a lista de composição pra dar contexto ao erro; só o botão que falha calado.
O ponto de fundo é que oculto do catálogo ≠ não vendável dentro do kit. Se a ideia era barrar componente desligado, available já cobre isso e já era checado antes da PR. Eu tiraria visible do gate. Se houver motivo pra mantê-lo, que seja pelo menos visible === false && available === false, e nunca sozinho.
🟠 min_quantity no kitItemFields só tem um consumidor, e é o errado
Os dois lugares que precisam de min_quantity no código novo o sobrescrevem — use-product-card.ts:153 e use-product-details.ts:102 passam a quantidade exigida pelo kit. Sobra um único consumidor real: o parseProduct, em parse-product.ts:24:
quantity: minQuantity > 0 ? Math.max(minQuantity, quantity) : quantity,Antes o campo não vinha no fields, então isso era inerte. Agora, um componente com min_quantity: 6 numa composição que pede 1 unidade gera item de carrinho com quantity: 6, enquanto kit_product.composition[].quantity e pack_quantity seguem em 1 — o carrinho passa a exibir quantidade e subtotal inflados. No checkout o fix-items.ts:122 já derrubava esse item de qualquer jeito, então não é dinheiro perdido; é o cliente vendo um carrinho que não corresponde ao que vai ser cobrado.
Nenhum dos 30 componentes de kit da tia sônia tem min_quantity > 1 hoje, então não materializa lá — mas é latente pra qualquer loja. Eu simplesmente tiraria o campo do kitItemFields.
🟠 isSkuSelected responde "sim" enquanto os itens do kit ainda estão carregando
use-product-details.ts:116-118 decide pelo kitComposition, mas durante o carregamento todo slot tem product: null → variations: [] → isSelected: true. Resultado: isSkuSelected fica true, o checkVariation deixa passar, o loadToCart espera o load e só então descobre que falta SKU — e devolve o mesmo isFailedToCart genérico, sem o alerta de campo pendente que existe justamente pra isso.
O isLoadingKitItems já está exposto pra resolver isso; falta consumi-lo no gate — isSkuSelected retornar false (ou o addToCart aguardar) enquanto o load não terminou.
🟠 O seletor de variação some quando a variação escolhida zera
KitComposition.vue:62-72 encadeia o aviso de esgotado e o seletor no mesmo v-if/v-else-if, e isInStock (use-product-details.ts:97-104) já leva a variação escolhida em conta. O caminho fica: cliente escolhe o tamanho P → P está sem estoque → o bloco vira "esgotado" e o <select> desaparece → não há como voltar e trocar por M. O kit inteiro fica travado numa escolha ruim.
Aviso e seletor precisam conviver, não se substituir.
🟠 O agrupamento de produto repetido monta um carrinho que o checkout rejeita
O corpo da PR lista "agrupa produtos repetidos na composição" como resolvido, mas o fix-items.ts não aceita o formato. O use-product-card.ts:158-163 funde por _id + variation_id; do outro lado, fix-items.ts:186-195 calcula packQuantity como a soma das quantidades da composição e fix-items.ts:214 valida kitTotalQuantity % (minPacks * packQuantity) === 0.
Para uma composição [A×1, A×1, B×1]: o cliente manda A=2 e B=1 (total 3); o servidor calcula packQuantity = 3 e minPacks = 2, cai em 3 % 6 ≠ 0 e remove todos os itens do kit (fix-items.ts:230-239).
Não é regressão — o código antigo também não passava nessa validação. Mas vale alinhar: o caso que de fato funciona é o mesmo produto com variações diferentes (aí as chaves não fundem e o servidor aceita), e a forma suportada de pedir duas unidades é uma entrada só com quantity: 2. Ou ajusta o texto da PR, ou trata de verdade — o que exigiria mexer no fix-items.ts junto.
🟠 Pré-existente, mas está exatamente na função que a PR reescreve: comprar N kits mostra o preço de 1
use-product-card.ts:152,156 acumula packQuantity += (comp.quantity || 1) * quantityToAdd, ou seja, já multiplicado pelo número de packs. Aí o shopping-cart.ts:156 faz final_price = kit_product.price / pack_quantity e divide pelo total inflado.
Comprando 2 kits de R$100 com composição [A×1, B×1]: pack_quantity = 4, final_price = 25, e o carrinho exibe R$100 em vez de R$200. O fix-items.ts:217,223 recalcula pack_quantity sem multiplicar pelos packs, então o pedido sai certo — a divergência é só no carrinho, mas é o cliente vendo um valor e pagando outro.
A aritmética é idêntica à do código antigo, então não é regressão desta PR. Só que a tia sônia tem 18 kits vivos com seletor de quantidade, e a correção aqui é não multiplicar quantityToAdd no packQuantity — uma linha, dentro de uma função que você já está reescrevendo. Se preferir deixar pra outra PR, tudo bem, mas vale abrir a issue.
🟢 Minors
use-product-card.ts:108usa!kitItem?.availableeuse-product-details.ts:98usaavailable !== false— mesma regra escrita de dois jeitos, e a UI pode marcar como disponível o que o carrinho recusa.use-product-card.ts:144— a mesma referência do arraycompositionvai em todos os itens do kit e oadd-cart-item.ts:48só faz cópia rasa; mutar um mexe em todos.use-product-card.ts:165—Object.keys(...).map((key) => cartItemsByKey[key])éObject.values(cartItemsByKey).map(...).use-product-card.ts:90-96—getKitItemStocksó considera a variação quando a composição fixa uma; pra componente com variação livre usa o estoque agregado do pai, então oproduct.quantitydo kit fica otimista.use-product-card.ts:283— o memoloadingKitItemsnunca invalida no sucesso: enquanto orefetchStockatualiza o produto-kit, os componentes ficam com o estoque da primeira carga pelo resto da sessão.use-product-details.ts:143— oas Array<Record<string, any>>joga fora o tipo à toa;use-shipping-calculator.ts:51já aceita(ShippedItem | CartOrProductItem)[].use-product-details.ts:162— oif (kitShippedItems.length)mantém o produto-kit noshippedItemsquando o load falha, e o frete volta a ser calculado com o item sem peso, que é justamente o bug que a PR resolve.KitComposition.vue:71— o slot expõe{ item, selectVariation }mas nãohasSelectionAlert, então o tema não consegue reproduzir o destaque de campo pendente que o<select>de fallback tem.KitComposition.vue:50—target="_blank"semrel="noopener".
Pra entrar antes do merge
- Tirar
visibledo gate domatchKitItem— é o único item que quebra loja em produção. - Tirar
min_quantitydokitItemFields— não tem consumidor útil e distorce oparseProduct. - Gate de loading no
isSkuSelected, e o seletor de variação convivendo com o aviso de esgotado noKitComposition. - A PR do tema. Procurei em
ecomplus/storee ela ainda não existe (só a #61 do renovate e a #26 do A/B). Sem ela, esta PR entrega só a validação mais rígida e nenhuma UI de seleção — e como todoProductCard.vuechamaloadToCart(1)semkitVariationIds, kit com componente com variação fica sem caminho de compra em qualquer tema. As duas precisam subir juntas.
⚠️ Nota de deploy — tia sônia
A correção de estoque está certa, mas muda número em produção no dia que subir. Rodei os dois algoritmos sobre o catálogo da loja 1024: 16 dos 18 kits visíveis passam a anunciar bem menos estoque. Amostra (snapshot de hoje — são números vivos):
Kit Granola Premium com Chocolate 726 → 90 Kit Amor que nutre 289 → 15
Kit Clássico da Torcida 726 → 202 Kit Sabor que nutre 289 → 42
Kit com 5 Granolas - Linha Sabores 726 → 96 Kit Granola Zero Low Carb 432 → 118
Kit Granola Sabores / Mais Sabor 726 → 74 Kit Cookies Chips 219 → 40
Boa notícia: nenhum kit sai do ar. Os dois com componente zerado ("Kit mais Coquinho e Castanha" e "Kit Sabores do Brasil") já apareciam como 0 antes — o break em !kitItem.quantity do código antigo já os zerava. A mudança é só de teto de estoque, para menos, que é o ponto.
Ainda assim vale avisar a loja: quem olha o painel vai ver o estoque dos kits despencar de um dia pro outro.
A barra do céu (loja 3967) não tem nenhum produto com kit_composition — varri os 5.040 produtos. Está fora de risco, apesar do kit_composition no fields do SelectedProductsSection.astro:38.
…it prices on cart Address review points from PR #799: - Keep hidden (`visible: false`) kit components buyable, they're commonly gifts/addons kept out of the catalog on purpose; - Stop fetching `min_quantity` for kit items so `parseProduct` can't inflate cart item quantity over the composition required quantity; - `isSkuSelected` no longer answers true while kit items are still loading, and `addToCart` awaits the kit items load to properly alert pending variation selection instead of failing silently; - Variation selector is kept together with the out of stock warning so the customer can switch to another variation when one runs out; - `kit_product.pack_quantity` is no longer multiplied by the number of packs to add, fixing cart displayed price when buying 2+ kit units; - Each kit cart item gets its own `composition` array copy to prevent shared reference mutations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ep kit stocks fresh Remaining review points from PR #799: - Kit stock ceiling for items with free (unfixed) variation now uses the best variation stock instead of the parent aggregated quantity, which sums all variations and oversells the kit; - Kit items load memo is cleared on settle so later calls refetch fresh stocks instead of keeping the first load for the whole session; - Availability rule aligned between cart parsing and details UI (`available === false`), so the UI can't offer what the cart refuses; - Kit `shippedItems` no longer falls back to the weightless kit product when the composition items aren't loaded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
leomp12
left a comment
There was a problem hiding this comment.
Voltei nos quatro commits. Os nove pontos da review anterior estão fechados — conferi cada um contra o código, não contra a mensagem de commit — e sobraram duas regressões, ambas pequenas e dentro deste repo.
Os nove pontos anteriores: fechados ✅
| Ponto | Correção |
|---|---|
🔴 gate de visible |
fora de kitItemFields e de matchKitItem |
🟠 min_quantity no fields |
removido |
🟠 isSkuSelected durante o load |
use-product-details.ts:118 + addToCart aguarda o load |
| 🟠 seletor sumindo ao esgotar | v-else-if → v-if |
| 🟠 agrupamento de repetido | corpo da PR qualifica o caso suportado |
| 🟠 preço de N kits | use-product-card.ts:170, packQuantity += quantityPerPack |
🟢 available inconsistente |
alinhado em available === false dos dois lados |
🟢 composition compartilhada |
composition.map((item) => ({ ...item })) |
🟢 Object.keys().map |
Object.values() |
O que validei além de ler:
- O bloqueante, contra dado real. Na tia sônia (1024) o componente
RV0078da cartela de adesivos seguevisible: false, available: true, quantity: 219. Comvisiblefora do gate, o kitPIC5533volta a ser vendável e o teto saimin(30, 29, 219) = 29kits. Correto. min_quantity: sem o campo nos fields,parse-product.ts:17calculaminQuantity = 0e a linha 29 vira identidade. O inflar de quantidade não tem mais como acontecer.pack_quantity: refiz a aritmética contra o servidor nos dois sentidos. Kit de R$100 com[A×1, B×1]comprando 2 → cliente mandapack_quantity: 2e exibe 2×50 + 2×50 = R$200;fix-items.tscalculapackQuantity = 2,minPacks = 2,4 % (2*2) = 0, passa, e cobra os mesmos R$200. Testei também[A×2, B×1]com 1 e 2 packs. Fecha. A divergência carrinho×pedido acabou, e o valor novo é justamente o que ofix-itemsjá esperava.- Composição repetida: variações distintas passam (
3 % (1*3) = 0), mesmo produto+mesma variação continua caindo em3n % 6n ≠ 0. O texto da PR está correto. removeCartItemnão dependia da referência compartilhada dacomposition— usa a própria cópia como checklist. A cópia profunda é segura ali.
E os pontos que levantei e não se sustentaram, para você não perder tempo: hasSelectionAlert está declarado em Props e é exposto no render context do <script setup>, então o v-bind compila; o reduce do getKitItemStock está guardado por variations?.length, nunca roda em array vazio, e a seed 0 é conservadora; limpar o memo no .finally() não abre corrida nem apaga kitItems num refetch que falhe; e nenhum consumidor de addToCart usa o retorno ou depende de execução síncrona.
O que regride — os dois que eu resolveria aqui
1. Subtotal do frete na página de kit
main mandava [{...kitProduct, quantity}] para a calculadora: subtotal = preço do kit, correto. A PR troca pelos componentes (use-product-details.ts:167), e o subtotal é derivado da própria lista em use-shipping-calculator.ts:117-121:
const baseAmountSubtotal = computed(() => {
return shippedItems.value.reduce((subtotal, item) => {
return subtotal + getPrice(item) * item.quantity;
}, 0);
});Como price está em kitItemFields, cada componente entra com o preço avulso. Isso vira body.subtotal (use-shipping-calculator.ts:221), e o app consome direto: custom-shipping-calculate.ts:116 faz amount = params.subtotal || 0, usado em :197 na regra de frete grátis (amount >= rule.min_amount) e em :225 na taxa percentual.
O peso melhora — era o objetivo, e está certo. O valor piora, e sistematicamente na mesma direção, porque kit quase sempre custa menos que a soma das partes: kit de R$100 com componentes de R$80 + R$70 anuncia frete grátis num piso de R$120, e no checkout o subtotal real é R$100 e o frete aparece.
Some setando final_price: kitProduct.price / packQuantity nos itens enviados ao cálculo, ou passando baseParams.subtotal com o preço do kit.
2. shippedItems fica vazio para sempre se o loadKitItems falhar
O b0f41f4b tirou a guarda if (kitShippedItems.length) antes do splice (use-product-details.ts:165-167). Se a API falhar, kitItems fica null → todos os product da composição são null → a lista fica [] e não volta mais.
Aí use-shipping-calculator.ts:219 não seta body.items nem body.subtotal, e o POST sai só com o CEP. Pelo menos o app da mandae rejeita explicitamente (mandae-calculate.ts:170-175, CALCULATE_EMPTY_CART), o que cai em scheduleRetry() (:300, timeout de 10s) → novo erro → novo retry, indefinidamente. A página de produto não passa canAutoSubmit, então só começa se o cliente digitar o CEP — mas antes o fallback [kitProduct] ao menos gerava request válida.
A guarda volta em uma linha, ou não substitui enquanto kitItems.value for null.
Parte 2 (temas): o que precisa entrar junto com o bump
Não é bloqueio desta PR — o fluxo é cloud-commerce primeiro, lojas depois com os pacotes publicados. Mas vale registrar o escopo, porque nenhum caller passa kitVariationIds hoje:
store/.../ProductCard.vue:76→@click.stop.prevent="loadToCart(1)", sem os IDs. Idem nos cinco temas deecomplus-stores/e noCartSuggestedProductCard.vuedo barradoce.barradoce:278,tiasonia:271eefacini:142usamuseProductCarddireto e chamamloadToCart(quantity, { variationId })— a página de produto dessas três também precisa de ajuste.- O tema padrão,
mundoquadrieforjaestelarusamuseProductDetailsmas não renderizam oKitComposition, entãoisSkuSelectedficafalsee os dois caminhos de compra param (ProductDetails.vue:47levapreventDefault,:57devolvenull).
Só afeta kits cujos componentes têm variação — os de componente simples, como o PIC5533, seguem funcionando em todas as superfícies. Mas o kit de demonstração da própria PR (DEMO-NICHO-kit-camisaria-masculina, três componentes has_variations: true) é exatamente o caso que para.
Duas coisas que ajudam nessa segunda parte:
- O bump não é um passo humano.
store.renovate.json:41-52temautomerge: truepara@cloudcommerce/*minor/patch, e os seis repos herdam o mesmo preset. Como isto sai comofix(storefront), a versão entra sozinha nas lojas — vale sincronizar as PRs dos temas com a publicação, não depois dela. - O
ProductCardnão tem como saber que aquele kit exige seleção de SKU.hasVariationséfalsepara o produto-kit, e é justamente o gate usado hoje (ProductCard.vue:69). Uma flag tipoisKitSkuRequirednouseProductCardresolve num lugar só e deixa a PR dos temas trivial — essa parte seria aqui.
Follow-ups (não são regressão, dá para testar junto da parte 2)
- A corrida que pode desfazer a correção
max→min.productStockFields(use-product-card.ts:33-38) incluiquantitye o watcher fazObject.assign(product, productStock); orefetchStocké debounced em 1200ms e oloadKitItemsresolve bem antes. A sequência gravaproduct.quantity = min(...)(:326) e ~1s depois ofreshStocksrestaura oquantitypróprio do kit.mainjá faziaproduct.quantity = maxKitQntno fim doloadKitItems, então é pré-existente — mas é a que eu olharia primeiro das quatro, porque põe em risco justamente o ganho principal da PR. Guardar o teto num ref próprio, em vez de mutarproduct.quantity, resolve os dois lados. isFailedToCartnunca é resetado (use-product-card.ts:375), e os temas fazem:disabled="isFailedToCart"/v-if="... && !isFailedToCart". Depois da primeira falha não há como disparar um novoloadToCartpara limpar a flag: só recarregando a página. Pré-existente, mas a PR multiplicou osreturn nulldo caminho de kit.- A composição nunca vai no HTML servido (
use-product-details.ts,onMounted). O bloco "Este kit contém" não indexa, os skeletons trocam por conteúdo com layout shift, e o teto de estoque só aperta depois da hidratação. Os IDs já estão no doc SSR ($storefront.apiContext.doc), então dá para buscar do lado do servidor — mas é decisão de escopo, não defeito. - Um kit dispara N eventos
add_to_cart, cada um com o preço avulso do componente (use-analytics.ts:299-304; nesse instante ofinal_pricerateado ainda não foi calculado). Kit de R$100 reporta R$150 em três eventos para GA4, Meta e TikTok. Pré-existente e a correção não é local a esta PR, mas é o buraco que sobra na consistência ponta a ponta. - Produto repetido com a mesma variação: o
checkInStocké por entrada e o item de carrinho é mesclado, então[{A,1},{A,1}]com estoque 1 passa duas vezes e montaquantity: 2. É exatamente a configuração que o corpo da PR já documenta como não suportada e que ofix-items.ts:214rejeita — fica como limitação conhecida.
Menor: o corpo da PR ainda diz que kitItemFields "passa a trazer variations, visible, base_price e min_quantity" — visible e min_quantity saíram nos commits novos.
Uma coisa que conferi e está certa, para constar: não há adição parcial. O parseKitCartItems valida a composição inteira e devolve null antes de qualquer addCartItem (use-product-card.ts:359-368), então nunca sobra meio kit no carrinho. E o split de addProductToCart em parseProduct + addCartItem é equivalente — addProductToCart é literalmente os dois em sequência (shopping-cart.ts:43-49) —, então a reordenação do kit_product não perdeu comportamento pelo caminho.
… items fail to load Kit composition items sent to the shipping calculator had their standalone prices, so the shipping subtotal (used on free shipping and percentage fee rules) was the sum of the items prices instead of the kit price. Items now get `final_price` as the kit price split by pack units, the same `kit_product.price / pack_quantity` split of cart items. `baseParams.subtotal` can't be used, `useShippingCalculator` always overwrites it when there are items. When kit items fail to load, the kit product itself is kept as shipped item, instead of an empty list sending shipping requests with no items at all (refused by apps such as Mandae with `CALCULATE_EMPTY_CART`). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPBH9xuT8qtMvtNUor3M1L
…refresh On product pages the kit product stock is refreshed (debounced) after hydration, and the fresh `quantity` was assigned over the kit packs ceiling set by `loadKitItems`, undoing the lowest item stock limit about 1s after page load and letting buyers pick more kits than its items allow. The kit packs ceiling is now kept apart and reapplied whenever the product `quantity` is set, keeping `product.quantity` as the contract for themes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPBH9xuT8qtMvtNUor3M1L
…icked Kit products have no variations themselves, so `hasVariations` is false and product cards try to add them straight to cart, which fails for kits with items whose variations are not fixed by the kit composition. `useProductCard` now exposes `isKitSkuRequired`, true when any composition item `has_variations` with no fixed `variation_id`, for themes to send the buyer to the product page to pick sizes instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPBH9xuT8qtMvtNUor3M1L
… page Adds vitest to the storefront package, with kit tests through `useProductCard` and `useProductDetails` mocking only the API client (and tracking events): kit stock ceiling (also after the stock refresh), `isKitSkuRequired`, adding 2 kits to cart and the kit page shipping items (subtotal and load failure). Storefront tests are excluded from the root `tsconfig.test.json`, run by other packages tests (`modules` on CI), since they depend on storefront `@@sf` aliases. Workspace dependencies must be built to run them (`@cloudcommerce/api` lib). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPBH9xuT8qtMvtNUor3M1L
|
@vitorrgg assumi daqui os pontos que ficaram da última review: 4 commits por cima do branch, sem rebase.
Atualizei o corpo do PR: saíram Falta antes do merge: o teste manual escolher tamanho → carrinho no kit demo da 1011, e o PR de tema ( |
leomp12
left a comment
There was a problem hiding this comment.
Aprovando depois de revisão adversarial e validação do merge com a main atual.
- Merge com
main(vitest 3.2.7, lockfile novo): sem conflito, build ok, 6/6 testes, ESLint limpo; nenhum teste do storefront entra notsconfig.test.jsonda raiz. - Sem regressão introduzida: produto comum segue idêntico; kit simples (componente
visible: falseincluso) segue vendável em todos os temas; carrinho → checkout bate com ofix-items.ts(pack_quantitypor pack, preço de N kits); os 8 apps de frete leem osubtotal/final_pricerateado, o mesmo que o checkout envia. - API:
kit_composition[].has_variationsvolta nosearch/v1(fields padrão e__), então oisKitSkuRequiredserve aos cards;product.quantitysegue como contrato do teto para os temas. - Exposição: das lojas com tema no monorepo, só a demo 1011 tem kits com item de variação livre. As demais não mudam para o cliente até o PR de tema, que deve sair junto com o próximo release.
Follow-ups, sem bloquear: tipagem de shippedItems para a prop do ShippingCalculator, link do KitComposition para componentes ocultos (visible: false) e o dimensions.forEach pré-existente em jadlog/datafrete.
Problema
Produtos do tipo kit (com
kit_composition) eram renderizados na seção de detalhes como um produto comum: a composição nunca era listada e, quando os itens do kit tinham variações, eles iam para o carrinho sem SKU selecionado.Exemplo real (loja demo 1011,
DEMO-NICHO-kit-camisaria-masculina): três camisas, cada uma comhas_variations: truee 5 tamanhos — não havia como escolher o tamanho de cada peça.Junto com isso, a camada de dados tinha outros defeitos:
loadKitItemspegava o maiorfloor(estoque / qnt)entre os itens em vez do menor, liberando mais kits do que os componentes permitem. Na página de produto, o refetch de estoque pós-hidratação ainda sobrescrevia esse teto com o estoque do próprio produto-kit cerca de 1s depois.kit_productera gravado depois doaddCartItem, então um componente já presente no carrinho era mesclado e passava a ser tratado como item de kit.pack_quantityera multiplicado pelo número de kits.Mudanças
use-product-card.tskitItemFieldspassa a trazervariationsebase_price; itens ocultos do catálogo (visible: false, comum em brindes) seguem vendáveis dentro do kitloadKitItemsdeduplica IDs, é idempotente e usa o mínimo entre os itens, respeitandokit_composition[].variation_idfixo; o teto fica guardado à parte e é reaplicado quando o refetch de estoque atualiza o produtoloadToCartaceitakitVariationIds, valida SKU e estoque item a item, funde produto repetido na composição em um único item de carrinho (suportado quando as variações são distintas e as entradas repetidas têm a mesma quantidade — outras combinações, como o mesmo produto+variação duplicado, não passam na validação depack_quantitydofix-items.tsno checkout) e montakit_product(comvariation_idemcompositionepack_quantityde um pack) antes de inserir no carrinhoisKitSkuRequired:truequando algum item da composição tem variações não fixadas pelo kit, para oProductCarddos temas não tentar adicionar o kit direto ao carrinhouse-product-details.tsisKit,kitComposition,selectKitVariationeisLoadingKitItemsonMountedisSkuSelectedpassa a exigir variação escolhida em cada item do kit (e ficafalseenquanto os itens carregam), reaproveitando o alerta já existenteshippedItemspassa a ser a composição real do kit, comfinal_price= preço do kit rateado pelas unidades do pack (o mesmo rateio do carrinho), para o subtotal do frete — usado nas regras de frete grátis — ser o preço do kit; se os itens não carregarem, o próprio produto-kit segue como item de freteKitComposition.vue(novo, em@@sf/components)variationspara o tema injetar seu próprio seletor de SKU, com<select>como fallbackTestes (novo)
packages/storefrontganha vitest (pnpm --filter @cloudcommerce/storefront test, com as dependências do workspace buildadas) etests/kit-composition.test.ts, cobrindo teto de estoque (inclusive após o refetch),isKitSkuRequired, carrinho com 2 kits e frete na página de kit (subtotal e falha no carregamento)tsconfig.test.jsonda raiz excluipackages/storefront/tests, que dependem dos aliases@@sfdo storefrontTema
A renderização em si exige alterações correspondentes nos temas —
ecomplus/storee as lojas emecomplus-stores/— que precisam subir junto com a publicação desta versão, já que o renovate faz automerge de@cloudcommerce/*:ProductDetails.vue(temas comuseProductDetails): incluir o<KitComposition>passando oSkuSelectordo tema no slot, esconder o "Comprar agora" direto em kits (o link porproduct_idnão explode a composição) e usari19buyKitno CTAProductCard.vue: incluir!isKitSkuRequiredna condição do botão de compra diretauseProductCarddireto na página de produto (tiasonia, barradoce, efacini) precisam passarkitVariationIdsquando tiverem kits com itens com variaçãoSó afeta kits com itens de variação livre; kits de itens simples seguem funcionando em todas as superfícies.
Verificação
tscsem erros novos nos arquivos alterados/kit-camisaria-tres-basicasresponde 200 com o bloco da composição renderizado, e uma página não-kit segue idênticaspecificationsde cada componenteFalta um teste manual do clique/hidratação (escolher tamanho → adicionar ao carrinho) antes do merge.
🤖 Generated with Claude Code
https://claude.ai/code/session_01CPBH9xuT8qtMvtNUor3M1L