Playwright now follows menu sub-page links (brunch, lunch, dinner, etc.)
AND detects ordering platform links (order.online, toasttab, grubhub, etc.)
on restaurant websites. Visits ordering pages to scrape item-image pairs,
then fuzzy-matches them to menu items extracted from the main site.
This gives us complete menus from the restaurant's own website plus
food photos from their ordering platform — best of both worlds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strip common suffixes like "Order pickup and delivery" and embedded
street addresses from business names. Clean city field when it contains
state/zip/country (e.g. "Santa Monica, CA 90405, USA" → "Santa Monica").
Fixes applied in both CFML parser and JS frontend as safety net.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Feature cancelled — modifier wording handles the use case instead.
Removes IsInvertedGroup from SELECTs, JSON responses, RemovedDefaults
computation, and KDS/portal display logic. DB column left in place.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The JSON-LD fast path only got items/categories/prices but no modifiers.
Removing it lets Uber Eats pages fall through to Claude AI extraction
which handles modifiers like every other platform.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Removed IsInvertedGroup toggle and badge from menu builder UI
- Fixed saveFromBuilder.cfm not updating Name for template modifiers
(only selection rules were saved, name changes were silently lost)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
attachDefaultChildren now detects IsInvertedGroup=1 on parent items
and skips auto-adding defaults. The Android client explicitly sends
only the modifiers the user kept checked, so auto-adding was
re-inserting items the user had unchecked (e.g., Mustard).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The inverted group header item isn't always an order line item itself,
so RemovedDefaults was never computed. Now detects inverted groups
via children's ParentIsInvertedGroup flag and attaches RemovedDefaults
to the first child as a proxy. KDS JS handles both patterns.
Also skips showing default modifiers from inverted groups since those
are represented by "NO removed-item" instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
23 businesses were being cut to 20 when sorted alphabetically (no location),
hiding Win~Dow Venice and other names near end of alphabet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Items with NULL StationID now appear in all station views instead of
being hidden when a specific station is selected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
KDS was keeping the station filter from a previous business in
localStorage, causing orders to be hidden when switching businesses.
Now tracks businessId and resets station selection on change.
New businesses also get a default "Kitchen" station so the KDS works
out of the box.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
JS new Date() was parsing dates without Z as local time, showing UTC
values as-is. Adding Z suffix tells JS the dates are UTC so it converts
to the user's local timezone automatically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of clicking each menu item (broken by virtual scrolling),
extract item IDs from embedded JSON and make direct fetch() calls
to the itemPage GraphQL endpoint. 5 concurrent requests per batch.
Much faster and 100% reliable - no DOM interaction needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pass extracted item names via temp JSON file so Playwright knows exactly
what to click instead of guessing from DOM selectors (7 → 171 items)
- Use TreeWalker for exact text matching and aggressive scrolling
- Better price parsing: handle cents (int), dollars (string), displayPrice
- Improved modal dismissal with overlay click fallback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New doordash-modifiers.js: stealth Playwright script that clicks each
menu item on a DoorDash page, captures itemPage GraphQL responses,
and extracts optionLists (modifier groups with options and prices)
- Wire modifier extraction into DoorDash fast-path in analyzeMenuUrl.cfm:
after parsing items/categories, runs modifier script and maps results
- Improved business info extraction: address, phone, and hours now use
position-based parsing of StoreHeaderAddress, StoreHeaderPhoneNumber,
and StoreOperationHoursRange embedded data (fixes intermittent missing info)
- Add playwright-extra and stealth plugin to package.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
StorePageCarouselItem uses imgUrl, but MenuPageItem uses imageUrl.
This gives ~540 item images instead of 26.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
listToArray treats delimiter as individual chars, not a string.
Rewritten to use position-based find() traversal for proper
multi-character delimiter splitting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract items from MenuPageItemList (171 items) instead of StorePageCarouselItem (54)
- Categories already mapped to items via MenuPageItemList sections
- Cross-reference images from carousel entries by item name
- No need for Claude category assignment - data already structured
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract menu data directly from embedded JSON in DoorDash HTML:
- Categories from MenuBookCategory entries
- Items with names, descriptions, prices, and image URLs from StorePageCarouselItem
- Business info from page title and StoreHeaderAddress
- Uses Claude to assign items to categories
- Upgrades image URLs to 600px for better quality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update render.js on dev server to scroll page before capturing images
- Increase Playwright wait from 4s to 5s and timeout from 90s to 120s
- Upsize DoorDash CDN thumbnails from 150px to 600px when downloading
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude sometimes returns category or modifiers as objects instead
of strings. Added isSimpleValue checks to prevent cast errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude response was being truncated mid-JSON for larger menus,
causing parse errors. The Lazy Daisy menu needed >8192 tokens.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The PDF upload goes to analyzeMenuImages.cfm (not analyzeMenuUrl.cfm).
Added control character cleaning, smart quote replacement, and Jackson
fallback parser with error handling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lucee's deserializeJSON fails on certain Claude outputs and the error
bypasses inner cftry/cfcatch. Parse via Jackson from file instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add smart quote/dash replacement for PDF-sourced text
- Add Jackson fallback parser for when Lucee's deserializeJSON fails
- Strengthen prompt to request properly escaped JSON
- Clean control characters more selectively
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lucee's deserializeJSON chokes on control characters in description
fields that JSON.stringify escapes but Lucee can't parse.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Default-checked items nested inside modifier groups were not being found
because the function only looked one level deep. Now it recurses through
all child items to find defaults at any depth.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add IsDeleted=0 filter to existing item search query
- Add support for OrderLineItemID parameter for direct item targeting
- Fixes duplicate items being created on quantity increment
- Fixes item deletion not working due to finding wrong/deleted records
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For inverted display: when user deselects a default-checked item, keep it
in cart with Quantity=0 instead of deleting. Cart displays "NO X" for these.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For inverted groups, when user deselects a default item, keep it in cart
with Quantity=0 instead of deleting. This allows cart to display "NO X".
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Child items need to know if their parent group is inverted for proper
display logic (showing "NO X" instead of listing selected items).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Required for inverted modifier display (showing "NO X" for removed defaults
instead of listing all selected items).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When deselecting a modifier (IsSelected=false), Quantity was incorrectly
set to 1 instead of 0, preventing the removal logic from working.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Works app can now display inverted modifier groups with "NO" prefix
for removed defaults, matching KDS behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CFML was failing to compile analyzeMenuUrl.cfm because ' contains
a # character that Lucee interprets as variable expression start.
Escaped all 4 occurrences to &##39;.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When enabled on a modifier group, KDS and cart only show removed
defaults (e.g., "NO Mustard") instead of listing all selected items.
Useful for groups where all options are checked by default.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>