Mobile Home v2 — Design review, pass 30

Nothing is ever hidden — the badge does all the work

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.

Badge legend Discount New Free delivery
9:41MySnacks100%

Home

Favourites 4 saved
VendorsD2a · D3a · D4

Discover

Categories
New things to try
Getting popular
Highly rated
All products

Cart

Profile

AP
Alex Petrosyan
ServiceTitan
Confirm order
📍
Delivery address
🗓️
Delivery time
Today, 14:30
Items
Used
Recommended
Remaining

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 moves from Home to Discover

First section, right below search — with real breathing room

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.

Quick-add's touch target, actually fixed

24px real button, 18px visible dot — not padding tricks

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.

Curated section titles now open a "see all"

Same chevron, same drill-in pattern as Favourites

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.

Discover gets three curated rows back

New things to try / Getting popular / Highly rated, then All products

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.

Quick add, everywhere a product is a card

A lone + at zero, a − N + stepper past that

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.

Search bar gets Slack-style @mention chips

Typed filters live inside the bar, not a dropdown underneath it

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.

Search-result products are cards again

Same gridCardHtml/.prod-grid as pre-search, not a list row

Product 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.

Discover drops "Browse categories"

One less section before "All products"

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.

Allergies + undesired merged; avoided vendors added

One tri-state ingredients page, one new vendors page

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's Next button was missing padding

Now matches Checkout's "Confirm order" button

.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.

Vendor card on Home trimmed to name + desc + badges

Stars, review count, and ingredients moved out — not deleted

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.

A tilted arch replaces the vendor border

A shallow curve, not a ruled line

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.

Vendor cards go full-bleed, minimize dropped

No border, no side inset, no collapse chevron

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.

Favourites is now a real drill-in, not a dead end

Tap the section header, get the same grid as Vendors/Categories

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."

Balance ring, reworked from the web version

Press-and-hold, no anchored tooltip

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 caps ingredients at 3

A "+N …" chip folds the rest, expands in place on tap

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.

News & Announcements, new on Home

A swipeable strip between Favourites and Vendors, dots below

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.

Filter pills + sheet removed

Search covers it now — a dropdown filter would be redundant

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: infinite scroll + back-to-top

"All products" loads in batches of 6 as you scroll

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.

No hide-conflicts control at all, on request

Discover now behaves exactly like every other browsing surface

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 warning now names the ingredient

No more generic "contains an allergen" — ever

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.

A wizard on first run, an editor in Profile, one shared picker

"Allergies & Preferences" was chosen over "Meal Preferences"

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.

Real taxonomy gap

Only 9 of the 11 D6 ingredients are eligible choices — on purpose

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.

Three severities, three colors

Red for allergy, indigo for an avoided vendor, amber for undesired ingredients

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.

Scope flag

No backend design exists for this yet

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.

Answers: why the distinction?

Eligibility isn't a mode the customer picks — it's the address

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.

Matches the real app

One "Confirm order" sheet, not a new checkout flow

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.

Scope note

"Change" on time, and the confirm action itself, are illustrative

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.

AI Assistant

Robot butler FAB, real chat input, new-conversation reset

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.

D9 (revised)

Search lives in a Discover tab, not a floating pill

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.

D6

Vendor ingredient chips are derived, not hand-picked

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.