A high-performance, accessible web application for natural language food search, structured nutritional comparison, and grounded food inquiry across 124,145 Canadian Open Food Facts products.
Ask-OFF WebApp is the official open-source frontend application for the Ask-OFF Canada ecosystem. It provides an intuitive, high-speed, and responsive interface designed to explore, analyze, and compare 124,145 Canadian grocery and packaged food products.
While traditional food catalogs present rigid, unstructured text fields, Ask-OFF delivers an interactive web experience powered by deterministic natural language search, official French/English bilingual product records, and evidence-grounded conversational inquiry.
- Conversational & Keyword Food Discovery: Instant natural language exploration coupled with debounced, race-condition-free autocomplete and query match highlighting.
- Deterministic Nutritional Transparency: Render official vector Nutri-Score gauges (A to E), NOVA industrial food processing groups (1 to 4), Eco-Scores, allergens, and per-100g nutritional facts tables.
- Side-by-Side Nutritional Comparison: Multi-product matrix evaluating 2 to 4 food items simultaneously with macro-nutrient deltas and persistent browser storage.
- Ask-OFF Assistant: Grounded conversational assistant (available via full page or floating action button) that queries the backend retrieval engine and provides verifiable citations to official Open Food Facts records.
- Zero-Latency Route Navigation: Route-level code splitting using
React.lazyandSuspenseto deliver minimal initial JavaScript payloads and lightning-fast transitions. - Universal Accessibility (a11y): Built from the ground up with keyboard navigation, visible focus indicators, screen-reader semantics, and full
prefers-reduced-motionaccommodation.
Ask-OFF WebApp operates as a decoupled single-page application (SPA) communicating over HTTPS REST APIs with the FastAPI retrieval backend.
flowchart TD
subgraph Browser_Client [Client Browser — AskOFF WebApp]
A[User Interaction] --> B[React Router 7 Declarative Routes]
B --> C[Pages & Lazy-Loaded Chunks]
C --> D[Shared Reusable UI Components]
D --> E[TanStack Query Cache]
D --> F[Local State: CompareContext & ListsContext]
end
subgraph API_Layer [Frontend API Boundary]
E --> G[Typed REST Client: src/api/client.ts]
G --> H[Vite Development Proxy /api or Production HTTPS]
end
subgraph Backend_Infrastructure [Ask-OFF Backend — offCanada]
H --> I[FastAPI REST Gateway]
I --> J[Query Understanding & Intent Pipeline]
J --> K[(OpenSearch 2.x BM25 Cluster<br/>124,145 Canadian Records)]
end
| Layer | Technologies | Purpose |
|---|---|---|
| Frontend Core | React 19.2+, TypeScript 6.0+, Vite 8.1+ | Ultra-fast modern web application with strict type safety and sub-second HMR |
| State & Data Fetching | @tanstack/react-query 5.101+, Context API | Server-state caching, stale-while-revalidate, request deduplication, and local storage |
| Routing & Code Splitting | React Router 7.18+, React.lazy, Suspense | Declarative route matching with discrete asynchronous chunks per route |
| Styling & Design System | TailwindCSS 3.4+, PostCSS 8, Autoprefixer | Utility-first responsive styling with custom typography, focus styling, and scrollbars |
| Icons & Media | Lucide React 1.24+, SVG Vector Graphics | Clean, accessible vector icons and dynamic Nutri-Score/NOVA badges |
| Testing Suite | Vitest 4.1+, @testing-library/react, JSDOM | Comprehensive unit and component behavior test suites (24/24 tests passing) |
| Static Code Analysis | Oxlint, TypeScript Compiler (tsc -b) | Sub-second linter execution and zero-error strict type verification |
| Route | Page Component | Key Functionality |
|---|---|---|
/ or /discover | LandingPage | Hero search, dynamic typewriter placeholders, brand chips, category highlights, and catalog showcase |
/search | SearchPage | Search results grid, 9 dietary & health filter toggles, sort dropdown, responsive drawer, and pagination |
/product/:id | ProductDetailsPage | Full product specifications, 4-tier CDN images, nutrition facts per 100g, ingredients, allergens, and labels |
/compare | ComparePage | Side-by-side comparison table for 2–4 products with nutrient difference calculations and quick product picker |
/offbot | OffBotPage | Dedicated conversational food assistant with verifiable ODbL citations, grounded evidence cards, and follow-ups |
/lists | ListsPage | Saved grocery lists, favorites, and dietary bookmarks with print and JSON export capabilities |
/recipes | RecipesPage | Curated recipe directory with ingredients linked directly to live search queries and barcode tokens |
/extensions | ExtensionsPage | Ecosystem showcase for community plugins, allergy advisors, barcode scanners, and grocery integrations |
/status | DashboardPage | Live backend service health check, OpenSearch connection status, record count, and autocomplete testing sandbox |
/about | AboutPage | Project mission, architecture pillars, Open Food Facts partnership, and Open Database License (ODbL) attribution |
The frontend is structured around reusable, accessible presentation components:
src/components/
├── ErrorBoundary.tsx # Catches unhandled render errors with user-friendly recovery UI
├── OffBotChat.tsx # Core conversational chat engine, message list & ODbL citations
├── OffBotWidget.tsx # Spherical Floating Action Button (FAB) launcher and popup chat modal
├── ProductCard.tsx # Food product card with image, brand, Nutri-Score & macro stats
├── ProductImage.tsx # Resilient image loader with 4-tier Open Food Facts CDN normalization
├── ProductImagePlaceholder.tsx # Category-aware SVG fallback placeholder for items without images
├── NutritionTable.tsx # Structured nutrition table per 100g with European & Canadian standards
├── NutriScoreLogo.tsx # Official vector SVG Nutri-Score spectrum graphic (A to E)
├── NutriScoreBadge.tsx # Compact Nutri-Score pills, NOVA Group cards (1 to 4) & Eco-Score badges
├── SearchBar.tsx # Search input with debounced autocomplete, typewriter & AbortSignal
├── FilterSidebar.tsx # Desktop sidebar and mobile slide-over drawer for dietary filters
├── Pagination.tsx # Accessible page navigator with previous/next controls
├── LoadingSkeleton.tsx # Animated skeleton loaders for cards, detail sheets, and grids
├── EmptyState.tsx # Friendly empty states with recommended actions
├── ErrorState.tsx # Connection error displays with retry callbacks
└── ScrollToTop.tsx # Window scroll reset upon route navigation
AskOFF-WebApp/
├── public/ # Static public assets
│ ├── favicon.svg # Web application favicon
│ ├── icons.svg # SVG sprite sheet
│ └── logo.png # Project logo asset
├── docs/
│ └── design-references/ # Design artifacts, reference screenshots & visual mockups
│ ├── A.jpg
│ ├── B.jpg
│ ├── C.jpg
│ ├── image (1).png
│ ├── image.png
│ ├── Nutri-score-A.webp
│ └── Screenshot 2026-08-26 180122.png
├── src/
│ ├── api/
│ │ ├── client.ts # Typed REST API client, timeout handling & CDN normalization
│ │ └── assistantService.ts # Conversational query synthesis & citation models
│ ├── assets/ # Optimized vector icons and project logo
│ │ ├── app_qr.svg
│ │ ├── hero.png
│ │ ├── logo.png
│ │ └── phone_app.png
│ ├── components/ # 16 reusable UI and utility components
│ ├── constants/
│ │ └── queries.ts # Default sample queries and animated typewriter phrases
│ ├── context/
│ │ ├── AssistantContext.tsx # Chat state, conversation history, and active product focus
│ │ ├── CompareContext.tsx # Multi-product comparison state (stored in localStorage)
│ │ └── ListsContext.tsx # Favorites, shopping list & saved items (stored in localStorage)
│ ├── pages/ # 10 route page components
│ ├── tests/ # Vitest unit and React Testing Library component tests
│ │ ├── assistantService.test.ts
│ │ ├── badges.test.ts
│ │ ├── client.test.ts
│ │ ├── components.test.tsx
│ │ └── productCard.test.ts
│ ├── App.tsx # Main router, ErrorBoundary, QueryClient, and Providers
│ ├── index.css # Tailwind directives, focus-visible styles, and custom scrollbars
│ └── main.tsx # Application entry point (React StrictMode)
├── .env.example # Template for frontend environment variables
├── .gitignore # Git exclusion rules
├── .oxlintrc.json # Oxlint linter configuration
├── package.json # Project manifest, scripts, and dependencies
├── package-lock.json # Locked dependency tree
├── postcss.config.js # PostCSS plugin configuration
├── tailwind.config.js # TailwindCSS theme and font configurations
├── tsconfig.json # TypeScript project configuration root
├── tsconfig.app.json # TypeScript browser configuration
├── tsconfig.node.json # TypeScript Node/Vite configuration
├── vite.config.ts # Vite configuration with proxy and Vitest settings
├── CONTRIBUTORS.md # Contributor guidelines and workflow
└── README.md # Project documentation
- Node.js:
v18.0.0or higher (v20 LTSrecommended) - npm:
v9.0.0or higher - Ask-OFF Backend API: Running locally on
http://127.0.0.1:8000or hosted remotely
git clone https://github.com/SaitejaKommi/AskOFF-WebApp.git
cd AskOFF-WebAppnpm installCopy the example environment configuration:
cp .env.example .env| Variable | Default | Description |
|---|---|---|
VITE_API_BASE_URL | /api | Base URL or proxy prefix for the Ask-OFF FastAPI backend. In development, Vite automatically proxies /api to http://127.0.0.1:8000. |
Security Note: All
VITE_*variables are bundled directly into client-side code and visible to anyone inspecting network requests. Never expose private passwords, tokens, or backend credentials in frontend environment variables.
npm run devThe application will launch with hot module replacement (HMR) at http://localhost:5173.
The repository enforces strict code quality, type safety, and automated test coverage:
# 1. Run all 24 automated unit & component tests
npm run test# 2. Run Oxlint static analysis (0 errors)
npm run lint
# 3. Verify TypeScript compilation
npx tsc -b
# 4. Compile production build
npm run build
# 5. Preview production bundle locally
npm run preview- Component Behavior: User-facing tests for ProductCard, NutritionTable, EmptyState, ErrorState, and ErrorBoundary via
@testing-library/react. - API Client Utilities: Validation of per-100g nutrient extraction, 4-tier CDN URL normalization, and product image resolution.
- Assistant Service: Context-aware nutritional reasoning, allergen extraction, and structured citation verification.
- Nutritional Standards: Enforcement of Nutri-Score calculation bounds (A to E) and NOVA food processing classifications (1 to 4).
- Asynchronous Route Splitting: Route components are dynamically imported using
React.lazy(), cutting the initial main bundle size down to 266 kB (81.5 kB gzip) and serving per-page JavaScript chunks on demand. - Race-Condition-Free Autocomplete: Search inputs use
AbortControllerto automatically cancel in-flight HTTP requests when the user continues typing, preventing stale network responses from overwriting newer suggestions. - Open Food Facts CDN Tiering: Barcode images with 9+ digits are normalized into 4-tier subdirectories (e.g.
0060383860479$\to$ 006/038/386/0479/1.jpg), preventing image delivery 404s. - Global Error Boundary: Client-side rendering exceptions are intercepted gracefully by ErrorBoundary, offering immediate recovery actions without blank screens.
- Production Deployment: The project compiles to standard static assets in
dist/that can be hosted on any static platform (Cloudflare Pages, Vercel, Netlify, AWS S3 / CloudFront, or NGINX) with client-side routing fallback to/index.html.
- CONTRIBUTORS.md — Comprehensive contributing workflow, component conventions, testing practices, and accessibility rules.
- docs/design-references/ — Design system assets, reference screenshots, and visual specifications.
This application interfaces with open product records provided by Open Food Facts, published under the Open Database License (ODbL). Individual product images and brand assets remain the property of their respective copyright holders under Open Food Facts contributor terms.
Ask-OFF is an independent community discovery project and is not operated by the Open Food Facts organization.
This project is licensed under the Apache 2.0 License.