Mobile Home v2 — Design review, pass 30
Tied to 2026-07-30-mobile-home-v2.md. Filtering by category/vendor/ingredient lives in Discover's
search bar (type @ for a chip), not a separate control — Home stays pure browse. Reload for the first-run
wizard asking about allergies/ingredients to avoid and vendors to avoid —
skippable, editable later from Profile. Every matching product always shows an exclamation badge naming the
specific ingredient or vendor, everywhere it appears. There's no separate "hide conflicts" control anymore — Discover
now behaves exactly like Home and vendor browsing, always showing every match. Checkout still mirrors the real
app's single "Confirm order" sheet from an earlier pass.
Reload for the first-run wizard, or use Profile → Allergies & Preferences once set. Tap a chip once for an allergy and you'll see a red ! badge — naming the ingredient — on matching products everywhere, including Discover search results, which are never hidden. In Discover, type @ in the search bar to filter by category/vendor/ingredient — it becomes a removable chip inside the bar. Tap a vendor's See all, a category, or any product to drill in. In Cart, tap Checkout to open the confirm sheet. The robot button between Discover and Cart opens the AI Assistant.
Categories now lives in Discover as its own section, first in line before New/Popular/Highly rated —
same .cat-row/.cat-card component, same tap-a-card-to-drill-in behavior, just
relocated. Home's own dedicated click listener for it was removed entirely rather than left dead: since
the row now sits inside Discover, the screen's existing delegated handler already covers it, and leaving
the old listener in place would have double-fired openCategoryScreen on every tap (caught
before it shipped). The default 18px top margin above the first section title is back — the previous
first section had it manually collapsed to 2px to sit tighter under the search bar, which read as
cramped rather than intentional.
The UX-review flagged the +/− buttons as an 18px touch target, well under the ~44px guideline. The
first fix attempt used an invisible ::before padded out to 32px — a common trick, but
verified (via real coordinate hit-testing, not just .click()) to do nothing: a click outside
an element's own box doesn't hit-test to it no matter what's painted there. The actual fix makes the
<button> itself 24px, with the 18px colored circle as a `pointer-events:none` inner
span purely for looks. 24px is as far as this goes without the two buttons in an active stepper touching
— confirmed 15px of real clearance between them at that size. Still short of 44px, which this card width
genuinely can't fit alongside a price and two buttons; a bigger card, not a bigger button, would be the
way to actually get there.
The three curated rows only showed 3-4 cards each with no way to see the rest of that bucket — tapping
the title itself now pushes a full grid of every product in that section, reusing the exact chevron
affordance and screen layout already established for Home's Favourites. The row and its "see all" screen
share one source of truth (CURATED_SECTIONS's getters), so the count in the header can never
drift from what the row actually shows. Tapping a card inside the row still opens that product directly,
same as before — only the title text is the new tap target.
A UX pass flagged that removing the filter pills (pass 26) also removed the only way to browse Discover
without already knowing the @ search syntax — the placeholder text hinting at it even gets
clipped mid-word at this width. These three rows restore a scroll-and-tap path back in, each backed by a
real (if mock-scale) signal rather than an arbitrary bucket: "New" reuses the existing new-product badge,
"Highly rated" ranks products by their own vendor's rating (Enjoy Kitchen's 4.9 and Figaro's 4.8 vendors
surface first, Ham-Gog's 4.6 falls out of the top 4 — an honest ordering, not padding). "Getting popular"
has no real signal to back it in this mock, so it's a dedicated trending flag set on a
handful of products by hand. All three reuse the exact same compact card (and quick-add stepper) as
Home's vendor sliders — same component, extracted once, used in four places now instead of copy-pasted.
Every product card — Home's favourites row and vendor sliders, every .grid-card in
vendor/category/favourites/search results and Discover's "All products" — now carries a quick-add
control next to the price: a single + when it's not in the cart yet (adds one, shows a toast), which
becomes a compact − N + stepper once it is, so a second or third of the same item doesn't require
opening the product page. The same product's control stays in sync across every card it appears on at
once (the same technique the favourite heart already uses), and it's wired through the same
cartItems the Cart screen's own stepper and Checkout already read — adjusting quantity from
either place updates both.
Typing @ opens a small picker for category/vendor/ingredient; typing @category:
(or picking "Category" from that first list) narrows it to a live-filtered list of categories — the same
pattern for the other two types. Picking one adds a chip inside the search bar (a real element
next to the text input, not the vendor/category suggestion rows that show up below the bar for plain
typed text) with an × to remove it, or Backspace on an empty input removes the last one. Chips of the
same type OR together, different types AND — and unlike the filter pills this replaced (which never
actually filtered anything in this mock), these chips do real work: they narrow Discover's product grid
live, combined with anything still typed in the bar. Vendor/category/ingredient quick-add suggestions for
plain free text are untouched; this is just a faster way to reach the same filters.
gridCardHtml/.prod-grid as pre-search, not a list rowProduct search results were squeezed into the same compact .sr-row line used for vendors,
categories, and ingredients — icon, name, a tiny static badge. That's fine for a vendor or an ingredient
(nothing more to show), but a product loses its thumbnail, price, and favourite heart. Products now
render with the exact card component used in "All products" and every other grid in the app, so typing a
search term doesn't change what a product result looks like — only vendors/categories/ingredients stay
as list rows, since those were never shown as cards to begin with.
Categories were already reachable as a Discover filter pill (and from Home), so a second "Browse categories" row of the same category cards, right above an "All products" grid, was redundant — pick a category as a filter instead of scrolling a row to find it. Discover's empty-search state is now just the infinite-scroll product grid, one section instead of two.
The wizard's two ingredient steps ("Do you have any food allergies?" / "Any ingredients you find undesirable?") picked from the identical 9-item list and were already mutually exclusive per ingredient — so they're now one page, with each chip cycling neutral → allergy → avoid-by-choice → neutral on tap. That freed a step for something new: a second page to mark vendors you'd rather avoid. Avoided vendors get the same "badge, don't hide" treatment as ingredient conflicts — their products still show up everywhere, just flagged, now in a third color (indigo) ranked between allergy and undesired-ingredient severity. Profile's editor mirrors both changes.
.wizard-btn-next had no padding at all — just flex:1 — so it collapsed to
text height while every other primary action in the app (like Checkout's pill-shaped "Confirm order")
uses 14px of padding for a substantial, easy-to-tap height. Added the same 14px padding here, so the
wizard's Next/"Save preferences" button now reads as the same weight of button everywhere else.
Both the ★ rating/review line and the ingredient chip row (with its "+N …" fold) are gone from Home's vendor card, on request — they still show up in full on the vendor's own screen (tap the name), so the information isn't lost, just no longer competing for space in a compact preview. This also retired the fold-to-expand interaction entirely, since it only existed to manage the ingredient row this card no longer has.
Going full-bleed removed the last visual break between one vendor and the next, so each pair of vendor cards is now separated by a thin arched curve (an SVG path, not a straight `<hr>`), tilted a couple degrees so it slopes gently downward left to right rather than reading as a level rule, with a visible soft drop-shadow underneath for a touch of lift. It only sits between cards — never trailing after the last one, since the "Categories" section header already closes that out.
Home's vendor cards were bordered boxes inset 20px from the screen edge, each with a chevron to collapse it back down — but every card always rendered pre-expanded, so that chevron only ever did one useful thing: hide content someone had to re-open. Cards now run edge-to-edge with no border, using whitespace alone to separate one vendor from the next; the name, rating, and ingredients still align to the same 20px column as every other section on Home, so nothing reads as misaligned even though the card itself no longer stops short of the screen edge.
Home's Favourites row only showed a horizontal strip with no way to see the full list — every other section (Vendors, Categories) already opens a dedicated screen when you tap in. Favourites now matches: tapping the header (a small chevron next to the count hints it's tappable) pushes a screen with every favourited product in the same two-column grid used elsewhere. Un-hearting an item while that screen is open removes it immediately, rather than leaving a stale card with an empty heart sitting in a list titled "Favourites."
The web app's circular spending ring is now on Home and Discover's headers, but the click-to-open tooltip (dark box, caret pointing back at the ring) is gone — a floating box anchored to a fixed header position doesn't hold up once the interaction moves. Instead, pressing and holding the ring grows it and carries it to the center of the screen, the background blurs behind it, and the same Used/Recommended/ Remaining breakdown fades in below — release anywhere to snap back. Dropped the lightning icon and relabeled the ring's caption from "Remaining" to "Balance," and the demo now shows a partial spend (1,200 of 4,000) so the ring's arc actually reads as progress instead of a solid, all-green circle.
Home's vendor card is a compact preview — the same ingredient list has plenty of room on the dedicated vendor screen (tap the vendor name), so only the Home card is capped. Past 3 chips, the extra ingredients fold into a dashed "+N …" chip; tapping it swaps in the full list for that vendor, no popover or second screen needed. One-directional (no re-collapse) since this is a preview list, not a filter you'd toggle back and forth.
Four cards — product news, a new vendor, a delivery perk, a maintenance heads-up — snap one at a time as you swipe, with dot pagination underneath tracking which card is in view. No auto-advance: a carousel that keeps nudging itself right under Favourites would be distracting on a screen people open several times a day. Tapping a card only shows a "not part of this design pass" toast — where each one should actually link (a promo, a vendor page, an in-app notice) is a product decision, not a layout one.
Discover's three filter pills (Categories/Vendors/Ingredients) and the tri-state bottom sheet they opened are gone. They were also documented as an interaction demo that never actually filtered Home or Discover's product results — the @mention chip search does that job for real, and does it from the same place a person is already typing, rather than a second control underneath. Nothing replaced the pills; search absorbed the job they were only pretending to do.
Discover's empty-search state is now just an "All products" grid, starting with 6 cards and loading 6 more each time you scroll near the bottom — reusing the same product-card component (and its conflict badge, favourite heart) as every other grid in the app. With only 11 products in this mock's data, that's two loads to reach the end, where a plain "you've reached the end" line appears rather than looping or duplicating products to fake more content. A round back-to-top button fades in once you've scrolled past ~500px (roughly a screen and a half) and scrolls smoothly back to the top on tap — instantly instead, if the OS has reduced-motion turned on.
An earlier pass added a "Hide items with my allergens or undesired ingredients" checkbox, checked by default, as a visible replacement for an even earlier silent-hiding mechanic. That checkbox itself has now been removed too — there is no hide/show control anywhere in Discover anymore. Every match always appears, exactly as Home and vendor/category browsing already worked; the exclamation badge (naming the specific ingredient) is the only conflict indicator anywhere in the app now, which also makes the app's behavior simpler to describe: nothing is ever filtered by allergy/undesired status, only flagged.
Every conflict surface — the exclamation badge's tooltip, the warning banner on product detail, even the compact badge inline in Discover's search-result rows — now states the specific ingredient(s), e.g. "Contains Milk (allergy) · Wheat (undesired)". A product can match multiple ingredients across both lists at once; all of them are named, not just the first match found.
It names both sections directly rather than making the customer guess what's inside (an alternative, if you want something shorter: "Dietary Preferences"). The first-run wizard is two skippable steps — one combined ingredients page where each of the same 9 chips cycles neutral → allergy → avoid-by-choice → neutral on tap (a single ingredient still can't be both, it just no longer needs two separate screens to say so), then a second page for vendors you'd rather avoid. Profile's editor mirrors the exact same two sections on one scrolling page instead of two steps, since editing benefits from seeing everything at once. Skipping the wizard doesn't force it to reappear — it can be filled in anytime from Profile, where a live summary ("2 allergies · 1 undesired · 1 avoided vendor" / "Not set") shows the current state without opening it.
The picker deliberately excludes Gluten-Free and Sugar-Free from both lists. Those two are "free-from" claims a product either has or doesn't (positive labels), not ingredients a person is allergic to or finds undesirable by that name — "I'm allergic to Gluten-Free" doesn't parse. Someone avoiding gluten today has to approximate with "Wheat," a reasonable proxy but not medically precise (gluten also lives in barley/rye, not just wheat). Getting this exactly right would need a genuine "Gluten" flag distinct from the "Wheat" contains-ingredient and matched against the "Gluten-Free" claim — flagging as a real, not-yet-resolved gap in the D6 ingredient taxonomy, not something this mock-up should paper over by inventing new ones unilaterally.
The exclamation badge (top-right of every product thumbnail, offset clear of the favourite heart where both appear) follows one ranked rule: allergy outranks an avoided vendor, which outranks an undesired ingredient — a product can trip more than one at once (an avoided vendor's product could also contain an allergen), and always renders the most severe color. Avoiding a whole vendor is a more deliberate call than skipping one ingredient by choice, so it sits above undesired but still below a medical allergy. This repeats across every surface a product appears — Home's vendor sliders, vendor/category/ingredient grids, product detail, cart rows, favourites, Discover's search-result rows, and even the AI Assistant's suggested products — so the warning is never surprising the one time it matters.
This is entirely client-side/in-memory in the mock. A real version needs a new customer-level
preference store (nothing like this exists today — the closest precedent is the
GenericAttributeService pattern already used elsewhere on Vendor, which could
plausibly extend to Customer), a save/read API endpoint, and a decision on whether allergy
data has any extra handling given its safety sensitivity (e.g., should support/ops ever see it, does it
need different retention treatment than an ordinary preference). None of that is resolved here — this
pass is UX and interaction only.
An earlier pass had a manual "Company lunch / Personal order" toggle in Cart, which misrepresented the mechanic — it made it look like the customer chooses an order type. They don't: a company-lunch order is just what you get when the delivery address happens to be one of the company's registered addresses (D3b); everything else is a personal order. That's now modeled correctly — the confirm sheet's address card is the single source of truth, and the warning only appears when the selected address is personal and no vendor is running a Free-Delivery promotion. Tap "Change" on the address card to see both states.
Real mysnacks-mobile checkout is a single bottom sheet titled "Confirm order" — address and delivery time are picked earlier (today, via Home's own pickers) and shown here read-only with a "Change" link, next to the item list and a submit button. This mock matches that shape exactly rather than inventing a new checkout pattern: same card layout, same "Change" affordance, same black "Confirm order" button. The one difference from production is what triggers the banner under the cards — real production shows an allowance/self-pay warning there; this mock repurposes that exact slot for the benefit-eligibility banner, since it's the same "surface something important right above the confirm button" pattern.
Changing the address is fully interactive here so the eligibility logic can be demonstrated. "Change" on delivery time just toasts, since Schedule picking isn't part of this redesign. Tapping "Confirm order" clears the cart and shows the real app's own success copy ("✅ Order confirmed") for fidelity — it isn't a proposal to change checkout itself, which stays out of scope for this Home redesign.
The FAB straddles the tab bar (half in, half overflowing into content), centered so it sits between Discover and Cart on this 4-tab bar rather than on top of any one tab. It opens a pushed screen with a scripted opening exchange (a pre-workout, low-carb/high-protein ask → three suggested products with one-tap "+ Add" → an offer to generate a 2-week plan), plus a real text input for follow-up questions and a reset icon to start over. This whole feature has no backend design yet — no macro/nutrition data exists in the label taxonomy, no recommendation endpoint, no plan-persistence concept — it would need its own design pass before going further than this mock.
An earlier pass used a Home-only floating search pill. With a 3-tab bar the AI FAB's center landed exactly on Cart, which read like it belonged to Cart specifically. Adding Discover as a 4th tab fixes that structurally — the bar's center now falls in the gap between two tabs — and gives search a permanent, reachable-from-anywhere home, now also carrying the Categories/Vendors/Ingredients filter pills that used to sit on Home. This is a real reversal of the original D9 decision, worth reflecting in the design doc if this direction sticks.
Ingredients live on each product; a vendor card's ingredient row is the union of its products' ingredients computed at render time — the "derived only" answer locked into D6. A vendor with several different products can get a crowded chip row — a real consequence worth having an opinion on.
Mock-up 30 — Categories moved from Home to Discover as its own section, first in line; fixed the cramped spacing between the search bar and whatever section comes first.