9.8 KiB
9.8 KiB
task, cycles, context, private, started, finished
| task | cycles | context | private | started | finished |
|---|---|---|---|---|---|
| Fix broken layout on /add-products-to-store--h:<storeHash> — products render outside their cards, sometimes don't load, and the card boxes appear huge but empty. | 5 | true | false | 2026-05-17T00:00:00Z | 2026-05-17T00:00:00Z |
files
- resources/js/Pages/AddProductsToStore.vue [lines 202-393, 395-459] — page template + scoped CSS for
.product-pick-card,.product-thumb,.sticky-bottom-bar. Product grid uses.row g-2withcol-12 col-sm-6 col-lg-4. Step 1 picker is where boxes look huge/empty. - resources/js/Components/Core/CardSimple.vue [lines 37-47, 79-97] — wrapper used 3× on this page. Has
height: 100%; display: flex; flex-direction: column;+.is-premium(default true) adds white translucent background + hovertransform: translateY(-2px). Hover transform on the surrounding card (when product list is inside it) makes the whole grid jitter. - resources/js/Components/Core/FileImage.vue [lines 1-38] — emits
<img>directly. Page wraps it in.product-thumbwith:deep(img) { width: 100%; height: 100%; object-fit: cover }. Ifphotourlisnull/missing,p.photourl && p.photourl[0]passes empty string → FileImage falls back. But ifphotourlis a string (not array),p.photourl[0]returns the first character — produces a 1-char src that 404s, briefly showing a blank box before@errorswaps in the fallback. This is the "sometimes does not load" symptom. - resources/js/Pages/BatchAddProducts.vue — sister page; reference for known-good layout patterns (cross-check spacing, container, image handling).
- ai-docs/dictionary.md — read first per repo CLAUDE.md. Update with any new findings about
FileImagearray-vs-string handling andCardSimplehover side-effects.
steps
- Read
ai-docs/dictionary.mdfirst (repo rule). Then openresources/js/Pages/AddProductsToStore.vueand reproduce mentally: the picker grid at lines 258–286 is not wrapped inCardSimple, so it sits directly inside.tf-container. Confirm by inspecting structure. - Image robustness (root of "sometimes doesn't load" + tiny/broken thumbs): In
AddProductsToStore.vuelines 264 and 349, the:srcbinding isp.photourl && p.photourl[0] ? p.photourl[0] : ''. Replace both occurrences with a safe accessor:Add a small helper:src="Array.isArray(p.photourl) ? (p.photourl[0] || '') : (p.photourl || '')"const firstPhoto = (v) => Array.isArray(v) ? (v[0] || '') : (v || '');in<script setup>and usefirstPhoto(p.photourl)/firstPhoto(r.photourl)in the template. - Thumb sizing (the "huge empty box"): The
.product-thumbis 56×56 and.product-thumb-smis 40×40 — these are fine in isolation, but.product-pick-cardusesd-flex gap-3 align-items-centerwith.flex-grow-1 min-w-0and aform-checkon the right. On narrow viewports (col-12), ifp.nameis long andtext-truncateis bypassed by Bootstrap's flex defaults, the row grows tall. Audit:- Ensure
.product-pick-cardhasmin-heightset (e.g.min-height: 84px;) so cards have a consistent floor and don't appear "empty huge" while image loads. - Add
align-items: center; min-width: 0;to the inner.d-flexwrapper (Bootstrap already does this, but verify). - Constrain the card body so the thumb cannot stretch: ensure
.product-thumbkeepsflex-shrink: 0(already set) and confirm it isn't being stretched byalign-items: stretchfrom any parent.
- Ensure
- Row equal-height (cards "huge but empty" when description is short): Bootstrap
.row.g-2makes cols equal-height by default (align-items: stretch). Each col contains a single.product-pick-cardthat fills the column. When ONE card in a row has a long name (wraps to 2 lines) the others stretch to match — those look "empty huge". Fix by either:- Adding
align-self: startto.product-pick-card, OR - Adding
.row.g-2 { align-items: flex-start; }scoped to the page (preferred — keeps cards as tall as their own content).
- Adding
- Products outside the box: Inspect
.tf-container(global class). If it providesmax-width+margin: 0 auto, the picker grid should sit inside it. Confirm the grid is INSIDE the<div class="tf-container mt-4">block — currently it is. But the.sticky-bottom-barat line 288 usesposition: sticky; bottom: 12px;with its own background — on small screens the sticky bar can overlap product cards if the parent hasoverflow: hidden. CheckCardSimple—.card-customhasoverflow: hidden(line 42). The sticky bar is OUTSIDE CardSimple so that's fine, but verify by browsing the rendered page that no card hasoverflow: hiddencropping the checkbox/title. - CardSimple hover transform causing visual "popping out":
.is-premium:hover { transform: translateY(-2px) }(CardSimple line 94-97) applies to the page-level CardSimples (target store header, search bar). When the user mouses over them they lift, which can look like products jumping out of the box. Either pass:is-premium="false"on the search-bar/header CardSimples on this page (they shouldn't lift), or scope the hover lift to a class that's only added when used as a clickable card. - Empty state height: Lines 254-257 —
text-center py-5 text-mutedfor the empty state. Ensurepy-5is enough that the empty state doesn't collapse into a thin strip after the search input. Likely fine; verify visually. - Loading state: Line 251-253 —
text-center py-5for the spinner. Same — fine. - Theming compliance (per CLAUDE.md): No
bg-white,bg-light, ortext-darkin this file — confirmed clean. Do not introduce them. - Sticky-top header offset: Page does not use
sticky-topon a child header — N/A. - VueRouteMap: Verify
AddProductsToStoreis registered inapp/Http/Controllers/Support/VueRouteMap.phpwith appropriateallowedUserTypes. If missing (the URL/add-products-to-store--h:…is reachable, so likely present), no change. Otherwise add it in the same commit. - After edits, run
npm run build(ornpm run devif Vite is hot-reloading) and visually verify in browser at/add-products-to-store--h:<a real store hash>. Confirm: (a) cards are uniform, hugging their content; (b) thumbnails always render (fallback kicks in when photourl is empty/string); (c) nothing overflows the page container; (d) sticky bottom bar stays inside the container. - Update
ai-docs/dictionary.mdwith: (a) note thatFileImageexpectsstring|array|nullforsrcand pages must pass via a safe accessor — neverarr[0]directly on an unknown shape; (b) note thatCardSimpleisis-premiumby default and lifts on hover — pass:is-premium="false"for non-interactive panels.
context
AddProductsToStore.vue — product card (Step 1 picker), lines 258-286
<div v-else class="row g-2">
<div v-for="p in filteredProducts" :key="p.hashkey" class="col-12 col-sm-6 col-lg-4">
<div class="product-pick-card" :class="{ picked: selected[p.hashkey] }"
@click="toggleProduct(p.hashkey)">
<div class="d-flex gap-3 align-items-center">
<div class="product-thumb">
<FileImage :src="p.photourl && p.photourl[0] ? p.photourl[0] : ''"
class="img-fluid rounded" alt="Product"
fallback="https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/146710fe9ece.bin" />
</div>
<div class="flex-grow-1 min-w-0"> … name / category / price … </div>
<div class="form-check"><input type="checkbox" … /></div>
</div>
</div>
</div>
</div>
AddProductsToStore.vue — scoped CSS, lines 395-459 (key blocks)
.product-pick-card { border: 1px solid …; border-radius: 12px; padding: 12px; cursor: pointer; }
.product-thumb { width: 56px; height: 56px; flex-shrink: 0; overflow: hidden; border-radius: 8px; }
.product-thumb :deep(img) { width: 100%; height: 100%; object-fit: cover; }
.sticky-bottom-bar { position: sticky; bottom: 12px; … border-radius: 14px; z-index: 5; }
FileImage.vue — relevant logic, lines 14-33
const processedSrc = computed(() => {
if (!props.src) return props.fallback;
if (Array.isArray(props.src)) return props.src[0]; // safe array handling here
if (props.src.startsWith("http") || props.src.startsWith("/") || props.src.startsWith("data:")) return props.src;
if (props.src.length > 20 && !props.src.includes(".") && !props.src.includes("/")) return `/RequestData/File/${props.src}`;
return props.src;
});
Key insight: FileImage itself already handles arrays. The bug is the caller unsafely doing p.photourl[0] — if photourl is a string, this yields one character.
CardSimple.vue — hover transform, lines 80-97
.is-premium { background: rgba(255,255,255,0.6); backdrop-filter: blur(12px); border: 1px solid rgba(0,0,0,0.05); box-shadow: 0 8px 32px 0 rgba(31,38,135,0.07); }
.is-premium:hover { border-color: var(--accent-color, rgba(83,61,234,0.3)); transform: translateY(-2px); }
is-premium defaults to true. Non-interactive panels (target store header, search bar) shouldn't lift.
notes
- dictionary: ai-docs/dictionary.md — MUST read first and update after (repo CLAUDE.md mandate).
- linters: eslint (via npm), no phpcs, tsc, pylint relevant for this Vue file. Run
npm run buildornpm run lintif available. - constraints:
- Repo CLAUDE.md: no
bg-white/bg-light/text-darkhardcoded. Theme via CSS vars. This file is currently compliant — keep it so. - Vue page edits don't require touching
routes/web.php,UserActions,UserPermissions, orVueRouteMapunless registration is missing. - Do NOT modify legacy Blade. All work is in
.vuefiles + scoped CSS. - Test direct URL access (reload at
/add-products-to-store--h:<hash>) after fix. - Keep edits minimal — fix the layout regressions, do not refactor the component.
- Repo CLAUDE.md: no