- 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>
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>
- Parse JSON-LD structured menu data from saved Uber Eats pages
(categories, items, prices, descriptions, business info)
- Show save-and-upload instructions when user pastes Uber Eats URL
- Always show header image upload step (was skipped for URL imports)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The WooCommerce fast path was returning empty business info. Now the
Playwright script extracts it from the page and the CFML passes it through.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- checkbox groups get MaxNumSelectionReq=0 (unlimited), radio/select get 1
- Pre-checked options (e.g. preselected condiments) saved with IsCheckedByDefault=1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The wizard checks item.imageUrl to decide whether to skip the image
upload step and download images from remote URLs instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detects WooCommerce sites from Playwright HTML (woocommerce, wc-add-to-cart,
tm-extra-product-options). Runs woo-modifiers.js which navigates all product
pages, extracts items with categories, and scrapes TMEPO/variation modifiers.
Falls through to Claude if extraction fails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- items.cfm: return BrandColorLight in menu response
- saveWizard.cfm: save BrandColorLight during business creation
- setup-wizard.html: second color picker for light brand color
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Post-process Claude menu extraction to detect when >60% of categories
have exactly 1 item (a common misparse). Collapses pseudo-categories
into the nearest preceding real (0-item) category.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added explicit guidance that categories are broad section headings
(5-15 typical) and items are individual products (30-150 typical).
Prevents Claude from treating each menu item as its own category.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude API rejects images when the declared media type doesn't
match the actual content. Now detects JPEG/PNG/GIF/WebP from
base64 magic bytes instead of trusting file extensions or
Content-Type headers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detect Grubhub URLs and fetch menu data directly via their
REST API instead of scraping HTML. Gets anonymous auth token,
then fetches full restaurant data including categories, items,
modifiers, prices, hours, lat/lng, tax rate, and item images.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Toast provides latitude/longitude in the location object. Extract
in analyzeMenuUrl.cfm and pass through to saveWizard.cfm, which
now includes lat/lng in the address INSERT. Skips the background
Nominatim geocode when coordinates are already available.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert cfthread from tag-based to cfscript syntax. Lucee 7
no longer allows </cfscript> mid-file to switch to tag mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parses upcomingSchedules from ROOT_QUERY.restaurantV2.schedule
and formats as "Mon 7:30am-6:30pm, Tue 7:30am-6:30pm" text string
that the setup wizard's parseHoursString() can consume.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add item.prices array support to first code path (was only checking
item.price scalar, but Toast now uses prices: [4.50] array)
- Extract individual address fields (addressLine1, city, state, zip)
from ROOT_QUERY restaurant data for saveWizard compatibility
- Update modifier extraction URL detection to match any toasttab.com
domain (not just order.toasttab.com)
- Update slug-based URL construction to use www.toasttab.com/local/order/
format instead of deprecated order.toasttab.com/online/ format
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract geocoding functions into shared api/inc/geocode.cfm and call
geocodeAddressById() via cfthread after every address INSERT or UPDATE.
Uses OpenStreetMap Nominatim (free, no API key). Non-blocking — the
HTTP call runs in a background thread so responses aren't delayed.
Affected endpoints:
- setup/saveWizard.cfm (new business creation)
- businesses/update.cfm (business address update)
- portal/updateSettings.cfm (portal settings save)
- addresses/add.cfm (customer delivery address)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tag stripping via reReplace on 1.7M HTML was likely causing the silent
failure on biz server. Brace-counting doesn't need tag stripping since
HTML tags don't contain { or } and attribute quotes come in balanced
pairs. Also removed var keywords from page-level cfscript (may not be
supported in Lucee at template level) and added detailed error output
to the cfcatch for debugging.
Also auto-detect dev/prod environment from hostname instead of
hardcoded flag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The regex .*? (non-greedy) fails on 500K+ JSON due to Java regex
backtracking limits, causing truncated data (only 3 of 6 menus
extracted). Replace all 3 extraction points with cfscript
brace-counting that reliably handles any JSON size.
Also decode HTML entities (& -> &, < -> <, etc.) from
Chrome View Source saves.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Chrome's Ctrl+U (View Source) saves wraps content in <span> tags
which breaks the regex termination pattern ;\s*window\. because
HTML tags appear between ; and the next window. variable.
Strip HTML tags from a working copy before regex extraction when
View Source format is detected (presence of <span id="line" tags).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Toast pages with multiple menus (e.g. "Food", "Beverages", "Merchandise")
now produce parent categories from the menu names with subcategories from
the groups within each menu, using the parentCategoryName field the wizard
already supports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When analyzing Toast menu pages, items with modifiers now have their
modifier groups extracted by clicking each item in a headless browser
and intercepting the GraphQL MenuItemDetails responses. Extracted
modifiers include group name, required/optional flag, min/max selections,
and option names with prices. Items sharing the same itemGroupGuid
inherit modifiers from successfully mapped siblings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Deliver to Table, Order Ready for Pickup, and Deliver to Address
to the default task types created by the setup wizard. These are
required by updateStatus.cfm to auto-create tasks at status 3.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Restaurant info is in ROOT_QUERY.restaurantV2(...) keys, not
Restaurant:* top-level keys (Apollo cache format)
- Prices are in item.prices array [4.50], not item.price scalar
- Added null checks for imageUrls (can be null, not missing)
- Fallback to title tag for business name
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of sending 450KB of HTML to Claude (which truncates to 100K
and only extracts ~60 items), parse the structured __OO_STATE__ data
directly on the server. This captures all menus, groups, items, prices,
and images from Toast pages - 169 items for Jus Family Cafe vs 60 before.
Falls back to Claude analysis if __OO_STATE__ parsing fails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Claude API sometimes returns explanatory text before the JSON
response even when instructed to return only JSON. Added extraction
logic to find the first { character and strip any leading text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Parse HTML heading structure to detect h2 parents with h3 subcategories
- Append detected hierarchy to Claude prompt as explicit hint
- Post-process Claude response to enforce hierarchy even if Claude returns flat
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- analyzeMenuUrl.cfm: Detect subcategories from Toast subgroups and
Claude API responses, preserve hierarchy with parentCategoryName
- setup-wizard.html: Display subcategories indented under parents
throughout wizard flow (categories step, items review, summary, preview)
- menu-builder.html: Show subcategories nested in outline modal view
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- saveCategory.cfm: Accept ParentCategoryID, enforce max 2-level nesting
- items.cfm: Include ParentCategoryID on virtual category rows for Android
- getForBuilder.cfm: Return ParentCategoryID in builder API response
- saveFromBuilder.cfm: Persist ParentCategoryID on save, track JS-to-DB id mapping
- saveWizard.cfm: Two-pass category creation (parents first, then subcategories)
- menu-builder.html: Parent category dropdown in properties, visual nesting in canvas,
add subcategory button, renderItemCard() extracted for reuse
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Large menus (20+ categories) were getting truncated JSON responses
at 8192 tokens, causing parse failures.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Tax rate: Use Zippopotam (free, no key) to get state, then lookup
from built-in state+local rate tables instead of API Ninjas
- Prices: Extract prices from Toast __OO_STATE__ MenuItem objects
when visible HTML prices are missing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When business info step loads with a ZIP code, automatically looks up
the combined sales tax rate and pre-fills the field. User can still
edit if needed. Field gets light green background to indicate auto-fill.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>