1<!DOCTYPE html><html lang="en" class="theme-compiled"><head><meta charSet="utf-8"/><link rel="stylesheet" href="https://stackedit.io/style.css"/><link rel="icon" href="/img/favicon.ico" type="image/x-icon"/><title>Innovation Projects Portfolio | Nariman Mani</title><meta name="description" content="Explore flagship projects and applied research initiatives led by Nariman Mani across AI, automation, and performance engineering."/><meta name="keywords" content="Nariman Mani, portfolio, projects, AI, automation, performance engineering, digital twins, adaptive testing"/><meta name="author" content="Nariman Mani"/><meta name="robots" content="index, follow"/><meta name="googlebot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"/><meta name="bingbot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"/><link rel="canonical" href="https://manistechmind.com/projects"/><link rel="alternate" type="application/rss+xml" href="https://manistechmind.com/feed.xml" title="Nariman Mani's Technical Mind RSS feed"/><link rel="alternate" type="application/feed+json" href="https://manistechmind.com/feed.json" title="Nariman Mani's Technical Mind JSON Feed"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="icon" href="/img/favicon.ico" type="image/x-icon"/><meta name="theme-color" content="#0f172a"/><meta http-equiv="content-language" content="en-us"/><meta name="generator" content="Next.js"/><link rel="me" href="https://scholar.google.com/citations?user=yaGTDkcAAAAJ&hl=en"/><meta property="og:type" content="website"/><meta property="og:title" content="Innovation Projects Portfolio | Nariman Mani"/><meta property="og:description" content="Explore flagship projects and applied research initiatives led by Nariman Mani across AI, automation, and performance engineering."/><meta property="og:site_name" content="Nariman Mani's Technical Mind"/><meta property="og:image" content="https://manistechmind.com/img/seo.png"/><meta property="og:image:secure_url" content="https://manistechmind.com/img/seo.png"/><meta property="og:image:alt" content="Project portfolio highlights for Nariman Mani"/><meta property="og:url" content="https://manistechmind.com/projects"/><meta property="og:locale" content="en_US"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:title" content="Innovation Projects Portfolio | Nariman Mani"/><meta name="twitter:description" content="Explore flagship projects and applied research initiatives led by Nariman Mani across AI, automation, and performance engineering."/><meta name="twitter:image" content="https://manistechmind.com/img/seo.png"/><meta name="twitter:image:alt" content="Project portfolio highlights for Nariman Mani"/><meta name="twitter:url" content="https://manistechmind.com/projects"/>
1<script type="application/ld+json">[{"@context":"https://schema.org","@type":"WebSite","name":"Nariman Mani's Technical Mind","url":"https://manistechmind.com"},{"@context":"https://schema.org","@type":"Person","name":"Nariman Mani","url":"https://manistechmind.com","sameAs":["https://scholar.google.com/citations?user=yaGTDkcAAAAJ&hl=en"]}]</script>
1<meta name="next-head-count" content="36"/><link rel="preload" href="/_next/static/css/2608fe212f892c8d.css" as="style"/><link rel="stylesheet" href="/_next/static/css/2608fe212f892c8d.css" data-n-g=""/><link rel="preload" href="/_next/static/css/6deff0ba3136d0ea.css" as="style"/><link rel="stylesheet" href="/_next/static/css/6deff0ba3136d0ea.css" data-n-p=""/><noscript data-n-css=""></noscript>
1<script defer="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script>
1<script src="/_next/static/chunks/webpack-9b312e20a4e32339.js" defer=""></script>
1<script src="/_next/static/chunks/framework-4556c45dd113b893.js" defer=""></script>
1<script src="/_next/static/chunks/main-7ca0c04a95757ac7.js" defer=""></script>
1<script src="/_next/static/chunks/pages/_app-70fd39700d9d72c3.js" defer=""></script>
1<script src="/_next/static/chunks/208-4cb088e41fa5f56b.js" defer=""></script>
1<script src="/_next/static/chunks/19-835143e27c6ddedf.js" defer=""></script>
1<script src="/_next/static/chunks/pages/projects-c57fb9bb381e53fd.js" defer=""></script>
1<script src="/_next/static/vlzqLRnNpWT2YeDoGTKKg/_buildManifest.js" defer=""></script>
1<script src="/_next/static/vlzqLRnNpWT2YeDoGTKKg/_ssgManifest.js" defer=""></script>
1</head><body class="antialiased text-lg bg-white dark:bg-gray-900 dark:text-white leading-base"><div id="__next"><span class="theme-bejamas"></span><div class="relative pb-24 overflow-hidden"><div class="flex flex-col items-center w-full max-w-6xl px-4 sm:px-6 lg:px-10 mx-auto"><header><div class="max-w-4xl mx-auto flex flex-col items-center px-4 sm:px-6 md:px-8 lg:px-16"><img alt="My photo" src="/img/PersonalPhoto-2.png" width="150" height="150" class="rounded-full object-cover"/><h1 class="text-2xl lg:text-2xl text-center mb-12 px-4 font-extrabold mt-2 px-6 py-2 2 text-transparent bg-clip-text shadow-lg transition-all duration-300 ease-in-out 3 hover:scale-105 hover:shadow-2xl bg-gradient-to-r from-gray-500 via-gray-400 to-gray-600">Nariman Mani, P.Eng., PhD Computer and Software Engineering</h1><nav class="mt-4 w-full"><ul class="flex flex-wrap justify-center gap-4 text-base sm:text-lg font-semibold"><li><a class="px-3 py-1 transition-all duration-300 rounded-md text-gray-800 hover:text-blue-500" href="/">Home</a></li><li><a class="px-3 py-1 transition-all duration-300 rounded-md text-gray-800 hover:text-blue-500" href="/posts">Posts</a></li><li><a class="px-3 py-1 transition-all duration-300 rounded-md text-gray-800 hover:text-blue-500" href="/about">About Me</a></li><li><a class="px-3 py-1 transition-all duration-300 rounded-md text-gray-800 hover:text-blue-500" href="/publications">Publications</a></li><li><a class="px-3 py-1 transition-all duration-300 rounded-md text-blue-400 font-bold border-b-2 border-blue-400" href="/projects">Projects</a></li><li><a class="px-3 py-1 transition-all duration-300 rounded-md text-gray-800 hover:text-blue-500" href="/learning">Learning</a></li><li><a class="px-3 py-1 transition-all duration-300 rounded-md text-gray-800 hover:text-blue-500" href="/reviews">Peer Review Contributions</a></li><li><a class="px-3 py-1 transition-all duration-300 rounded-md text-gray-800 hover:text-blue-500" href="/books">Books</a></li><li><a class="px-3 py-1 transition-all duration-300 rounded-md text-gray-800 hover:text-blue-500" href="/certifications">Certifications</a></li></ul></nav><div class="flex mt-6 bg-white justify-center dark:bg-gray-900 rounded-3xl p-1 transition-all"><button type="button" aria-label="Toggle Theme" class="flex items-center h-full px-4 py-2 bg-primary dark:bg-transparent rounded-3xl transition-all"><svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" fill="none" viewBox="0 0 21 20"><path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="stroke-current text-gray-400 dark:text-white" d="M19.5 10.79A9 9 0 119.71 1a7 7 0 009.79 9.79v0z"></path></svg></button></div></div></header>
3<script type="application/ld+json">{"@context":"https://schema.org","@type":"ItemList","name":"Innovation Portfolio","itemListElement":[{"@type":"CreativeWork","position":1,"name":"Analytic Hierarchy Process (AHP) Tool","description":"Overview Project Overview The Analytic Hierarchy Process (AHP) app is a Vite + React single-page application that helps teams create, edit, and review decision analyses directly in the browser. It ships with sample scenarios, includes an admin-only dashboard, and operates entirely on client-side storage so it can run on static hosting without server dependencies. Key capabilities Admin authentication gate protecting the dashboard and editing workflows. Creation and editing of AHP analyses with persisted data in the browser. Dashboard overview cards, recent analysis lists, and quick links into edit flows. Responsive UI composed from Tailwind CSS utilities, Radix UI primitives, and Shadcn-inspired components. Client-only data layer that seeds from JSON and syncs via localStorage (with an in-memory fallback for non-browser contexts). High-level architecture React SPA with hash-based routing: Uses react-router-dom v7 hash router so routes work on static hosts without custom rewrite rules. Feature organization: Routed pages live in src/pages, shared UI lives in src/components (with components/ui for primitives), and shared logic resides in src/hooks and src/utils. State and persistence: Authentication context tracks login status in localStorage, while the AHP data service provides CRUD operations backed by local storage and emits change events to keep views synchronized. Styling and visuals: Tailwind CSS powers layout and spacing, Radix UI primitives supply accessibility, and Recharts renders analytics visualizations. Included libraries (high level) React ecosystem: react, react-dom, react-router-dom Styling & UI: Tailwind CSS, Radix UI packages, tailwind-merge, clsx, lucide-react, framer-motion, tailwindcss-animate, react-resizable-panels, embla-carousel-react, react-day-picker, cmdk, sonner Forms & validation: react-hook-form, zod, @hookform/resolvers Data visualization: recharts Utilities and helpers: date-fns, class-variance-authority, input-otp, vaul, next-themes, prop-types Technical Details Technical Details Application structure Entry point: src/main.jsx mounts the React tree and global providers (e.g., toaster) before rendering the router defined in src/pages/index.jsx. Routing: Uses createHashRouter from react-router-dom v7. AuthProvider wraps the router, and RequireAuth protects all routes except /login. Layout shell: src/pages/Layout.jsx provides the global navigation, header, and content container shared by routed pages. Pages: - Dashboard.jsx renders overview cards (StatsOverview), recent analyses (RecentAnalyses), and actions to view/edit/delete analyses. - CreateAnalysis.jsx supplies the form-driven experience for building or editing AHP models, using validation helpers and shared UI primitives. - Login.jsx presents the admin authentication form. Authentication flow Implemented in src/hooks/useAuth.jsx via a React context and useAuth hook. Credentials come from VITEADMINUSERNAME and VITEADMINPASSWORD with safe defaults for local development. Auth state persists in localStorage under ahp:auth:v1; a constant-time password comparison reduces timing-leak risk. Protected routes redirect unauthenticated users to /login, and the context exposes login/logout helpers for UI components. Data layer and APIs Seed data lives in src/data/analyses.json and is loaded on first run. src/api/entities.js exports DecisionAnalysis, a service exposing CRUD operations against the stored analyses: - list(orderBy) sorts results (e.g., -createddate). - get(id) retrieves a specific analysis or throws if missing. - create(payload) inserts a new analysis, generating IDs and timestamps as needed. - update(id, updates) merges changes and refreshes the updateddate stamp. - delete(id) removes an analysis by ID. Storage writes to localStorage when available, with an in-memory fallback for environments without persistent browser storage. After mutations, the service dispatches a custom analyseschanged DOM event so dashboard and list views can refresh automatically. Styling and UI system Tailwind CSS provides utility-first styling; clsx and tailwind-merge help compose class names safely. Radix UI primitives and Shadcn-inspired components live under src/components/ui, covering dialogs, popovers, dropdowns, and other interactive elements. Icons use lucide-react; motion and animation rely on framer-motion and tailwindcss-animate. Charts and summaries on the dashboard use recharts for data visualization. Utilities and helpers src/lib/utils.js offers the cn helper for class name merging. src/utils contains routing helpers (createPageUrl, PAGEROUTES) and other shared logic reused across pages and components. Testing and quality ESLint configuration lives in eslint.config.js and runs via npm run lint to enforce consistency across React and hook usage. The project currently relies on manual and exploratory testing alongside linting; no automated test suite is configured in the repository. ChatOps interface lets teams approve or override automated fixes. Pluggable policy hooks
3support vendor-specific add-ons.","url":"https://manistechmind.com","image":"/img/ahp-new-2.png","datePublished":"2025","creator":{"@type":"Person","name":"Nariman Mani, P.Eng., PhD Computer and Software Engineering"},"keywords":"Deployment : Netlify, Vite, React, React Router, Tailwind CSS, Radix UI, React Hook Form, Zod, Framer Motion, Recharts, Embla Carousel, Lucide React, cmdk, Sonner, date-fns, next-themes, tailwindcss-animate, class-variance-authority, clsx, tailwind-merge, prop-types"},{"@type":"CreativeWork","position":2,"name":"Kids Read Fundations","description":"Overview KidsRead Project Overview What the project is KidsRead is a Next.js 14 application that acts as a joyful reading companion. It lets families track reading sessions, celebrate progress with photos and audio feedback, and administer lesson content from a single web experience. The app runs entirely on a Node/Next.js runtime (Heroku-friendly) and ships with built-in API routes for speech synthesis and image uploads so no separate backend service is required. Architectural layout Next.js pages router: The app uses the classic src/pages router for rapid rendering and API co-location. Public routes include the dashboard (/), reading workflow (/reading), history timeline (/history), and lesson admin (/admin). Shared chrome and navigation live in src/components/Layout.jsx and are applied via app.jsx. Feature views: Each route delegates to a corresponding view in src/views/. The views compose UI primitives, hooks, and data utilities to keep page files thin and focused on routing. State and data flow: Reading logs are managed client-side through the useReadingHistory hook, which persists to localStorage, computes streaks/aggregates with date-fns, and exposes CRUD helpers consumed by the dashboard and history views. Lesson content is sourced from static JSON in src/data/lessons.js and managed with useLessonManager. UI system: Visual components are built with Tailwind CSS and a set of shadcn-inspired primitives in src/components/ui/ (buttons, cards, badges, dialogs, etc.). Icons come from lucide-react. Layout and typography lean on Tailwind utility classes configured in tailwind.config.js and global styles in src/index.css. Utilities and helpers: API callers live in src/api/ to encapsulate HTTP details for speech generation and photo uploads. Shared helpers (formatting, randomization, demo data) reside under src/utils/ and src/lib/. Implementation walk-through App shell: app.jsx wraps every page with Layout, sets up Tailwind styles, and keeps navigation consistent. Layout.jsx renders the top navigation, active route highlighting, and a responsive content container. Dashboard (/): Dashboard.jsx surfaces streak metrics, calendar-driven log selection, recent celebrations, and an inline LogEditor modal. It pulls computed stats (minutesThisWeek, currentStreak, longestStreak, etc.) directly from useReadingHistory. Reading flow (/reading): Reading.jsx guides a single reading session with lesson details, timers, and a capture form. It can post photo evidence and speech feedback through the shared API utilities. History (/history): History.jsx renders the chronological feed of log entries with filters and summary badges, again powered by useReadingHistory state. Admin (/admin): Admin.jsx exposes lesson editing, visibility toggles, and ordering controls using useLessonManager, which manages the lesson list in memory for quick adjustments. Styling: src/index.css and App.css provide Tailwind base layers plus custom gradients, card shadows, and responsive tweaks. tailwind.config.js registers design tokens, font sizes, and animation keyframes for the UI kit. API surface (Next.js API routes) POST /api/speech (src/pages/api/speech.js) - Purpose: Streams OpenAI Text-to-Speech (gpt-4o-mini-tts) audio for any submitted text. - Input: JSON { text: string, voice?: string, format?: 'mp3' | 'opus' }. - Output: Binary audio (audio/mpeg or audio/ogg). Returns errors with helpful hints if the endpoint is unreachable or misconfigured. - Dependencies: Uses the openai SDK and Node streams; requires OPENAIAPIKEY. POST /api/upload-photo (src/pages/api/upload-photo.js) - Purpose: Accepts base64 image uploads and saves them to public/uploads on the Next.js server. - Input: JSON { fileName?: string, dataUrl: 'data:<mime>;base64,<payload>' }. - Output: JSON { url: string } pointing to the saved file for immediate rendering. - Notes: Uses Node fs/promises and path to ensure the upload directory exists and to sanitize filenames. Client-side API helpers fetchSpeechAudio in src/api/speech.js posts to /api/speech (or NEXTPUBLICSPEECHAPIENDPOINT override) and returns an object URL + MIME type for playback, with detailed error hints for routing/hosting issues. uploadPhoto in src/api/uploadPhoto.js posts base64 payloads to /api/upload-photo and returns the served URL so views can immediately render the new image. Key libraries and why they are used Next.js & React 18: Core app framework and rendering. Tailwind CSS + tailwindcss-animate: Utility-first styling and lightweight animations. shadcn/ui primitives (Radix UI + class-variance-
3authority + tailwind-merge): Accessible composable UI building blocks. lucide-react: Iconography throughout the dashboard and reading flows. date-fns: Date math for streaks, calendar highlights, and session summaries. react-hook-form & zod: Form handling and validation within editors and admin tools. OpenAI SDK: Server-side speech generation in the /api/speech route. embla-carousel-react, framer-motion, recharts: Interactive UI affordances (carousels, animations, charts) used across the views. Deployment notes The app builds with npm run build and runs with npm run start on the default Next.js port. Heroku deployment only needs a Node runtime and the OPENAIAPIKEY environment variable. No Netlify-specific files remain, and API routes are hosted by the Next.js server alongside the frontend. Technical Details KidsRead - Next.js KidsRead is now a Next.js application with built-in API routes for speech synthesis and photo uploads. The app ships with a dashboard, reading workflow, history tracking, and an admin view for lessons. Running locally The app runs on the default Next.js port (3000). API routes live under /api/* and run in the same server process. Environment variables | Variable | Purpose | | --- | --- | | OPENAIAPIKEY | Required for the /api/speech route that streams speech audio from OpenAI. | | NEXTPUBLICSPEECHAPIENDPOINT | Optional override for the speech endpoint. Defaults to /api/speech. | Speech service The /api/speech route calls OpenAI's gpt-4o-mini-tts model and returns binary audio. If you point NEXTPUBLICSPEECHAPIENDPOINT to a hosted endpoint, make sure that route accepts the same payload { text, voice, format } and returns audio bytes. Photo uploads Photo uploads now post to /api/upload-photo. The route accepts a { fileName, dataUrl } payload, writes the decoded bytes to public/uploads/, and responds with a URL the UI can render. On ephemeral hosts you may want to swap this storage for object storage (S3, etc.); the handler is a single file in src/pages/api/upload-photo.js. Building for production Deploy the built Next.js app to Heroku or any Node-friendly host. No Netlify-specific configuration or functions are required. For more information and support, please contact Base44 support at [email protected].","url":"https://manistechmind.com","image":"/img/kids-read-1.png","datePublished":"2025","creator":{"@type":"Person","name":"Nariman Mani, P.Eng., PhD Computer and Software Engineering"},"keywords":"Deployment : Heroku, Next.js 14, React 18, Tailwind CSS, tailwindcss-animate, shadcn/ui, Radix UI, class-variance-authority, tailwind-merge, lucide-react, date-fns, react-hook-form, zod, OpenAI SDK, embla-carousel-react, framer-motion, recharts"},{"@type":"CreativeWork","position":3,"name":"Nutri Scan","description":"Overview Nutri Scan A Next.js application that analyzes meal photos using OpenAI GPT-4o and keeps a nutrition history in a browser-backed JSON store. Prerequisites Node.js 18+ An OpenAI API key with access to the GPT-4o model (optional for mock responses) (Optional) A USDA FoodData Central API key for faster ingredient lookups (Optional) A wger API token for richer workout visuals and authenticated exercise lookups Getting Started Install dependencies: Create a .env file in the project root with your API keys. Server routes read OPENAIAPIKEY; providing NEXTPUBLICOPENAIAPIKEY enables the in-browser fallback without proxying through the server. If the keys are not provided the app falls back to a mock nutritional analysis and a small built-in ingredient library for testing purposes. Start the development server: Building for Production How It Works Image analysis: The upload flow converts photos to base64 data URLs and proxies them through a Next.js route that invokes OpenAI GPT-4o (vision + text) for calorie and macronutrient estimates with aggressive caching and configurable timeouts to reduce first-call stalls. Storage: Meals are stored in src/data/meals.json and synchronized to localStorage so the app behaves like an offline JSON database in the browser. Ingredient suggestions: Ingredient lookups and per-ingredient calorie estimates are proxied through a Next.js API route to the OpenAI API for c
3ontextual results. When the key is missing or OpenAI cannot be reached, the UI falls back to a curated set of common foods with per-gram nutrition data. Mock mode: When no API keys are configured, realistic mock responses are returned so the experience can be demonstrated without external calls. Technical Details Nutri Scan Technical Summary Application structure Framework: Next.js 14 App Router with React 18 client components. Shared layout, metadata, and styling live under app/ (including app/globals.css). Features: Route-to-screen mapping is centralized in src/features/pageConfig.js, while individual feature screens (Dashboard, Upload, History, Diet Plans, Workout Planner, Measurements, Admin) live in src/features/. State & auth: src/context/AuthContext.jsx manages lightweight session/role context for protected routes; preferences like storage mode hydrate on the client via src/lib/database/storageModeClient.js. Styling system: Tailwind CSS with tailwindcss-animate, Radix UI primitives, Lucide icons, and shadcn-inspired wrappers in src/components/ui. Additional UX helpers include framer-motion, cmdk, react-resizable-panels, next-themes, sonner, and input-otp. Data & storage Default JSON mode: Reads/writes meals and users in src/data/meals.json and src/data/users.json, falling back to /tmp if the repo path is unwritable. MySQL mode: Optional mysql2-powered persistence uses nutrimeals and nutriusers tables for multi-user deployments, with normalization helpers in src/lib/database/mysql.js. Client cache: Meals synchronize to localStorage so history and charts remain available offline or during API outages. API endpoints (under /api) POST /api/analyze â Accepts { imageDataUrl }; returns GPT-4o-derived ingredients, macros, micronutrients, and notes. POST /api/ingredient-suggestions â type=\"suggestions\" returns OpenAI/USDA-backed ingredient typeahead results; type=\"estimate\" returns per-ingredient calorie/macro estimates with normalized units and curated fallbacks. POST /api/upload-photo â Validates data URLs and returns a generated key plus inline URL for attaching images client-side. GET/POST /api/meals â Loads or persists the normalized meal store to disk or MySQL with ID and numeric coercion safeguards. GET/POST /api/users â Reads or writes user credentials/roles with ISO timestamps and safe disk fallbacks. GET /api/wger-muscles â Proxies the wger muscle catalog with pagination handling and one-hour caching. GET|HEAD /api/wger-asset â Safe proxy that whitelists hosts/paths and forwards cache/content headers for anatomy assets. Integrations and libraries OpenAI GPT-4o for image analysis, ingredient suggestions, and per-ingredient estimates (with deterministic mocks when keys are absent). USDA FoodData Central for ingredient lookups and nutrition fallbacks. wger API for muscles/exercise assets that support workout planning. Charts & data viz: recharts for nutrition/measurement charts; date-fns for date handling. Forms & validation: react-hook-form, @hookform/resolvers, and zod for schema-driven validation. Implementation highlights Nutrition services: src/server/nutrition.js centralizes OpenAI calls, caching, schema validation, and unit normalization for analysis/suggestion/estimate flows. Storage mode selection: getStorageMode chooses disk vs. MySQL at runtime, with helpers that sanitize payloads and normalize rows before persistence. Resilience: Mock responses and curated ingredient catalogs keep the app demoable without API keys; API routes use timeouts, caching, and safe fallbacks to avoid UI stalls. UI composition: Feature screens share consistent layout/navigation and brand constants from src/constants/app.js, while shadcn-style components keep Radix primitives themed and reusable. Exports playbooks for capacity planning and incident drills. Integrates CI checks that fail risky deployments automatically.","url":"https://manistechmind.com","image":"/img/nutri-3.png","datePublished":"2025","creator":{"@type":"Person","name":"Nariman Mani, P.Eng., PhD Computer and Software Engineering"},"keywords":"Deployment : Heroku, Next.js, React, Node.js, Tailwind CSS, Radix UI, OpenAI API, USDA FoodData Central API, wger API, mysql2"}]}</script>
3<main class="w-full px-4 sm:px-6 lg:px-16 text-slate-900 dark:text-white"><section class="w-full"><div class="mx-auto max-w-6xl text-center"><h2 class="text-4xl font-bold mb-6">ð Flagship Projects</h2><p class="text-lg text-slate-700 dark:text-white/80">A curated selection of innovation programs, research prototypes, and production-ready platforms I have architected or led.</p></div><div class="relative mx-auto mt-8 max-w-6xl overflow-hidden rounded-2xl border border-white/20 bg-white/10 p-4 sm:p-6 lg:p-8 shadow-lg backdrop-blur-lg dark:border-white/10 dark:bg-black/30"><div class="pointer-events-none absolute inset-y-0 left-0 w-16 bg-gradient-to-r from-white via-white/80 to-transparent dark:from-black dark:via-black/70" aria-hidden="true"></div><div class="pointer-events-none absolute inset-y-0 right-0 w-16 bg-gradient-to-l from-white via-white/80 to-transparent dark:from-black dark:via-black/70" aria-hidden="true"></div><div role="tablist" aria-label="Project selection" class="flex gap-2 overflow-x-auto rounded-xl border border-white/20 bg-white/70 p-2 pr-6 shadow-inner dark:border-white/10 dark:bg-white/5"><button type="button" role="tab" aria-selected="true" aria-controls="project-panel-5" id="project-tab-5" class="flex min-w-[180px] items-center justify-center gap-2 rounded-lg px-4 py-3 text-sm font-semibold transition focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-blue-500 dark:focus:ring-offset-transparent bg-blue-600 text-white shadow-lg shadow-blue-500/30"><span aria-hidden="true">ð¡</span><span class="truncate">Analytic Hierarchy Process (AHP) Tool</span></button><button type="button" role="tab" aria-selected="false" aria-controls="project-panel-4" id="project-tab-4" class="flex min-w-[180px] items-center justify-center gap-2 rounded-lg px-4 py-3 text-sm font-semibold transition focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-blue-500 dark:focus:ring-offset-transparent bg-white text-slate-700 hover:bg-slate-100 dark:bg-white/10 dark:text-white/70 dark:hover:bg-white/20"><span aria-hidden="true">ð¡</span><span class="truncate">Kids Read Fundations</span></button><button type="button" role="tab" aria-selected="false" aria-controls="project-panel-3" id="project-tab-3" class="flex min-w-[180px] items-center justify-center gap-2 rounded-lg px-4 py-3 text-sm font-semibold transition focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-blue-500 dark:focus:ring-offset-transparent bg-white text-slate-700 hover:bg-slate-100 dark:bg-white/10 dark:text-white/70 dark:hover:bg-white/20"><span aria-hidden="true">ð¡</span><span class="truncate">Nutri Scan</span></button></div><div id="project-panel-5" role="tabpanel" aria-labelledby="project-tab-5" class="mt-6 space-y-6"><div class="grid gap-6 lg:grid-cols-[minmax(0,1.2fr)_minmax(280px,0.95fr)] xl:grid-cols-[minmax(0,1.3fr)_minmax(320px,0.8fr)]"><div class="order-2 overflow-hidden rounded-2xl border border-white/30 bg-white/80 shadow-md shadow-slate-900/5 backdrop-blur dark:border-white/10 dark:bg-white/5 lg:order-1"><div class="bg-slate-50 dark:bg-white/5"><div class="w-full max-w-xl mx-auto"><div class="pb-2 text-center min-h-[4rem]"><div class="prose prose-sm mx-auto max-w-none dark:prose-invert"><ul> 4<li></li> 5</ul></div></div><div class="pb-2 flex justify-center space-x-2"><button class="h-3 w-3 rounded-full focus:outline-none bg-blue-600" aria-label="Go to slide 1"></button><button class="h-3 w-3 rounded-full focus:outline-none bg-gray-300" aria-label="Go to slide 2"></button><button class="h-3 w-3 rounded-full focus:outline-none bg-gray-300" aria-label="Go to slide 3"></button><button class="h-3 w-3 rounded-full focus:outline-none bg-gray-300" aria-label="Go to slide 4"></button></div><div class="relative overflow-hidden pt-2"><div class="flex items-start transition-transform duration-[1500ms]" style="transform:translateX(-0%)"><img src="/img/ahp-new-2.png" alt="Analytic Hierarchy Process (AHP) Tool showcase 1" class="w-full flex-shrink-0 object-contain object-top"/><img src="/img/ahp-new-1.png" alt="Analytic Hierarchy Process (AHP) Tool showcase 2" class="w-full flex-shrink-0 object-contain object-top"/><img src="/img/ahp-new-1.gif" alt="Analytic Hierarchy Process (AHP) Tool showcase 3" class="w-full flex-shrink-0 object-contain object-top"/><img src="/img/ahp-new-2.gif" alt="Analytic Hierarchy Process (AHP) Tool showcase 4" class="w-full flex-shrink-0 object-contain object-top"/></div></div></div></div></div><div class="order-1 rounded-2xl border border-white/30 bg-white/80 p-4 shadow-md shadow-slate-900/5 backdrop-blur dark:border-white/10 dark:bg-white/5 sm:p-6 lg:order-2"><div class="space-y-4"><p class="text-xs uppercase tracking-[0.15em] text-slate-500 dark:text-white/60">Quick facts</p><div class="grid gap-3 text-sm text-slate-700 dark:text-white/80"><div class="flex items-start gap-2 rounded-xl bg-white/70 p-3 dark:bg-white/5"><span aria-hidden="true">ð </span><div><p class="text-xs uppercase tracking-[0.12em] text-slate-500 dark:text-white/60">Year</p><p class="font-semibold text-slate-900 dark:text-white">2025</p></div></div><div class="flex items-start gap-2 rounded-xl bg-white/70 p-3 dark:bg-white/5"><span aria-hidden="true">
5ð§</span><div><p class="text-xs uppercase tracking-[0.12em] text-slate-500 dark:text-white/60">Role</p><p class="font-semibold text-slate-900 dark:text-white">Software Engineer & Researcher</p></div></div><div class="flex items-start gap-2 rounded-xl bg-white/70 p-3 dark:bg-white/5"><span aria-hidden="true">ð</span><div><p class="text-xs uppercase tracking-[0.12em] text-slate-500 dark:text-white/60">Project link</p><a href="https://manistechmind.com" target="_blank" rel="noopener noreferrer" class="font-semibold text-blue-600 hover:text-blue-500 dark:text-blue-300 dark:hover:text-blue-200">View live</a></div></div></div></div></div><div class="order-3 rounded-2xl border border-white/30 bg-white/80 p-4 shadow-md shadow-slate-900/5 backdrop-blur dark:border-white/10 dark:bg-white/5 sm:p-6 lg:col-span-2"><div class="flex flex-wrap items-center gap-3 border-b border-white/40 pb-4 dark:border-white/10"><div class="min-w-0 flex-1"><p class="text-xs uppercase tracking-[0.15em] text-slate-500 dark:text-white/70">Project</p><a href="https://manistechmind.com" target="_blank" rel="noopener noreferrer" class="text-xl font-semibold text-blue-600 hover:text-blue-500 dark:text-blue-300 dark:hover:text-blue-200">Analytic Hierarchy Process (AHP) Tool</a></div><span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600 dark:bg-white/10 dark:text-white/80">2025</span></div><dl class="grid gap-4 pt-4 text-sm text-slate-700 dark:text-white/80 sm:grid-cols-2 lg:grid-cols-3"><div class="space-y-1"><dt class="text-xs uppercase tracking-[0.15em] text-slate-500 dark:text-white/60">Role</dt><dd class="text-base font-medium text-slate-900 dark:text-white">Software Engineer & Researcher</dd></div><div class="space-y-1"><dt class="text-xs uppercase tracking-[0.15em] text-slate-500 dark:text-white/60">Link</dt><dd><a href="https://manistechmind.com" target="_blank" rel="noopener noreferrer" class="font-semibold text-blue-600 hover:text-blue-500 dark:text-blue-300 dark:hover:text-blue-200">View live</a></dd></div></dl><div class="mt-4 space-y-3"><p class="text-xs uppercase tracking-[0.15em] text-slate-500 dark:text-white/60">Tech Stack</p><div class="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4"><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Deployment : Netlify</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Vite</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">React</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">React Router</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Tailwind CSS</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Radix UI</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">React Hook Form</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Zod</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Framer Motion</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Recharts</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Embla Carousel</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Lucide React</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">cmdk</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">Sonner</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">date-fns</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">next-themes</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">tailwindcss-animate</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">
5class-variance-authority</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">clsx</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">tailwind-merge</span><span class="rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold text-slate-700 dark:border-white/10 dark:bg-white/5 dark:text-white/80">prop-types</span></div></div></div></div><div class="w-full rounded-2xl border border-white/30 bg-white/80 p-4 shadow-md shadow-slate-900/5 backdrop-blur dark:border-white/10 dark:bg-white/5 sm:p-6"><div class="flex gap-2 overflow-x-auto border-b border-white/30 p-3 sm:p-4 dark:border-white/10"><button type="button" class="whitespace-nowrap rounded-full px-4 py-2 text-sm font-semibold transition focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-transparent bg-blue-600 text-white shadow shadow-blue-500/30" aria-pressed="true">Overview + Technical</button><button type="button" class="whitespace-nowrap rounded-full px-4 py-2 text-sm font-semibold transition focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-transparent bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-white/10 dark:text-white/80 dark:hover:bg-white/20" aria-pressed="false">Requirements</button></div><div class="p-4 sm:p-6 ProjectPage_justifiedText__OWPZG"><div class="prose prose-sm sm:prose-base lg:prose-lg w-full max-w-none break-words text-slate-800 dark:text-gray-100 dark:prose-dark"><h2>Overview</h2> 6<h1>Project Overview</h1> 7<p>The Analytic Hierarchy Process (AHP) app is a Vite + React single-page application that helps teams create, edit, and review decision analyses directly in the browser. It ships with sample scenarios, includes an admin-only dashboard, and operates entirely on client-side storage so it can run on static hosting without server dependencies.</p> 8<h2>Key capabilities</h2> 9<ul> 10<li>Admin authentication gate protecting the dashboard and editing workflows.</li> 11<li>Creation and editing of AHP analyses with persisted data in the browser.</li> 12<li>Dashboard overview cards, recent analysis lists, and quick links into edit flows.</li> 13<li>Responsive UI composed from Tailwind CSS utilities, Radix UI primitives, and Shadcn-inspired components.</li> 14<li>Client-only data layer that seeds from JSON and syncs via <code>localStorage</code> (with an in-memory fallback for non-browser contexts).</li> 15</ul> 16<h2>High-level architecture</h2> 17<ul> 18<li><strong>React SPA with hash-based routing:</strong> Uses <code>react-router-dom</code> v7 hash router so routes work on static hosts without custom rewrite rules.</li> 19<li><strong>Feature organization:</strong> Routed pages live in <code>src/pages</code>, shared UI lives in <code>src/components</code> (with <code>components/ui</code> for primitives), and shared logic resides in <code>src/hooks</code> and <code>src/utils</code>.</li> 20<li><strong>State and persistence:</strong> Authentication context tracks login status in <code>localStorage</code>, while the AHP data service provides CRUD operations backed by local storage and emits change events to keep views synchronized.</li> 21<li><strong>Styling and visuals:</strong> Tailwind CSS powers layout and spacing, Radix UI primitives supply accessibility, and Recharts renders analytics visualizations.</li> 22</ul> 23<h2>Included libraries (high level)</h2> 24<ul> 25<li>React ecosystem: <code>react</code>, <code>react-dom</code>, <code>react-router-dom</code></li> 26<li>Styling & UI: Tailwind CSS, Radix UI packages, <code>tailwind-merge</code>, <code>clsx</code>, <code>lucide-react</code>, <code>framer-motion</code>, <code>tailwindcss-animate</code>, <code>react-resizable-panels</code>, <code>embla-carousel-react</code>, <code>react-day-picker</code>, <code>cmdk</code>, <code>sonner</code></li> 27<li>Forms & validation: <code>react-hook-form</code>, <code>zod</code>, <code>@hookform/resolvers</code></li> 28<li>Data visualization: <code>recharts</code></li> 29<li>Utilities and helpers: <code>date-fns</code>, <code>
29class-variance-authority</code>, <code>input-otp</code>, <code>vaul</code>, <code>next-themes</code>, <code>prop-types</code></li> 30</ul> 31<h2>Technical Details</h2> 32<h1>Technical Details</h1> 33<h2>Application structure</h2> 34<ul> 35<li><strong>Entry point:</strong> <code>src/main.jsx</code> mounts the React tree and global providers (e.g., toaster) before rendering the router defined in <code>src/pages/index.jsx</code>.</li> 36<li><strong>Routing:</strong> Uses <code>createHashRouter</code> from <code>react-router-dom</code> v7. <code>AuthProvider</code> wraps the router, and <code>RequireAuth</code> protects all routes except <code>/login</code>.</li> 37<li><strong>Layout shell:</strong> <code>src/pages/Layout.jsx</code> provides the global navigation, header, and content container shared by routed pages.</li> 38<li><strong>Pages:</strong> 39<ul> 40<li><code>Dashboard.jsx</code> renders overview cards (<code>StatsOverview</code>), recent analyses (<code>RecentAnalyses</code>), and actions to view/edit/delete analyses.</li> 41<li><code>CreateAnalysis.jsx</code> supplies the form-driven experience for building or editing AHP models, using validation helpers and shared UI primitives.</li> 42<li><code>Login.jsx</code> presents the admin authentication form.</li> 43</ul> 44</li> 45</ul> 46<h2>Authentication flow</h2> 47<ul> 48<li>Implemented in <code>src/hooks/useAuth.jsx</code> via a React context and <code>useAuth</code> hook.</li> 49<li>Credentials come from <code>VITE_ADMIN_USERNAME</code> and <code>VITE_ADMIN_PASSWORD</code> with safe defaults for local development.</li> 50<li>Auth state persists in <code>localStorage</code> under <code>ahp:auth:v1</code>; a constant-time password comparison reduces timing-leak risk.</li> 51<li>Protected routes redirect unauthenticated users to <code>/login</code>, and the context exposes <code>login</code>/<code>logout</code> helpers for UI components.</li> 52</ul> 53<h2>Data layer and APIs</h2> 54<ul> 55<li>Seed data lives in <code>src/data/analyses.json</code> and is loaded on first run.</li> 56<li><code>src/api/entities.js</code> exports <code>DecisionAnalysis</code>, a service exposing CRUD operations against the stored analyses:<!-- --> 57<ul> 58<li><code>list(orderBy)</code> sorts results (e.g., <code>-created_date</code>).</li> 59<li><code>get(id)</code> retrieves a specific analysis or throws if missing.</li> 60<li><code>create(payload)</code> inserts a new analysis, generating IDs and timestamps as needed.</li> 61<li><code>update(id, updates)</code> merges changes and refreshes the <code>updated_date</code> stamp.</li> 62<li><code>delete(id)</code> removes an analysis by ID.</li> 63</ul> 64</li> 65<li>Storage writes to <code>localStorage</code> when available, with an in-memory fallback for environments without persistent browser storage.</li> 66<li>After mutations, the service dispatches a custom <code>analyses_changed</code> DOM event so dashboard and list views can refresh automatically.</li> 67</ul> 68<h2>Styling and UI system</h2> 69<ul> 70<li>Tailwind CSS provides utility-first styling; <code>clsx</code> and <code>tailwind-merge</code> help compose class names safely.</li> 71<li>Radix UI primitives and Shadcn-inspired components live under <code>src/components/ui</code>, covering dialogs, popovers, dropdowns, and other interactive elements.</li> 72<li>Icons use <code>lucide-react</code>; motion and animation rely on <code>framer-motion</code> and <code>tailwindcss-animate</code>.</li> 73<li>Charts and summaries on the dashboard use <code>recharts</code> for data visualization.</li> 74</ul> 75<h2>Utilities and helpers</h2> 76<ul> 77<li><code>src/lib/utils.js</code> offers the <code>cn</code> helper for class name merging.</li> 78<li><code>src/utils</code> contains routing helpers (<code>createPageUrl</code>, <code>PAGE_ROUTES</code>) and other shared logic reused across pages and components.</li> 79</ul> 80<h2>Testing and quality</h2> 81<ul> 82<li> 83<p>ESLint configuration lives in <code>eslint.config.js</code> and runs via <code>npm run lint</code> to enforce consistency across React and hook usage.</p> 84</li> 85<li> 86<p>The project currently relies on manual and exploratory testing alongside linting; no automated test suite is configured in the repository.</p> 87</li> 88<li> 89<p>ChatOps interface lets teams approve or override automated fixes.</p> 90</li> 91<li> 92<p>
92Pluggable policy hooks support vendor-specific add-ons.</p> 93</li> 94</ul></div></div></div></div></div></section><div class="text-center mt-6"><a class="text-blue-500 hover:underline" href="/">Go back to home</a></div></main><footer class="py-16 flex flex-col items-center"><p class="dark:text-white uppercase mb-3 font-bold opacity-60">2026 All rights reserved.</p><div class="flex mt-6 bg-white justify-center dark:bg-gray-900 rounded-3xl p-1 transition-all"><button type="button" aria-label="Toggle Theme" class="flex items-center h-full px-4 py-2 bg-primary dark:bg-transparent rounded-3xl transition-all"><svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" fill="none" viewBox="0 0 21 20"><path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="stroke-current text-gray-400 dark:text-white" d="M19.5 10.79A9 9 0 119.71 1a7 7 0 009.79 9.79v0z"></path></svg></button></div></footer><div class="Layout_colorBackground__Adyt4 fixed top-20 opacity-40 dark:opacity-60"></div><div class="Layout_colorBackgroundBottom__9Yf_q absolute bottom-0 opacity-20 dark:opacity-10"></div></div></div></div>
94<script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"globalData":{"name":"Nariman Mani, P.Eng., PhD Computer and Software Engineering","blogTitle":"Nariman Mani's Technical Mind","author":"Nariman Mani","authorProfiles":["https://scholar.google.com/citations?user=yaGTDkcAAAAJ\u0026hl=en"],"footerText":"2026 All rights reserved."},"projects":[{"id":5,"title":"Analytic Hierarchy Process (AHP) Tool","descriptionSlug":"ahp-calc","tech":["Deployment : Netlify","Vite","React","React Router","Tailwind CSS","Radix UI","React Hook Form","Zod","Framer Motion","Recharts","Embla Carousel","Lucide React","cmdk","Sonner","date-fns","next-themes","tailwindcss-animate","class-variance-authority","clsx","tailwind-merge","prop-types"],"year":"2025","link":"https://manistechmind.com","role":"Software Engineer \u0026 Researcher","images":[{"image":"/img/ahp-new-2.png","description":"-","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Analytic Hierarchy Process (AHP) Tool showcase 1"},{"image":"/img/ahp-new-1.png","description":"-","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Analytic Hierarchy Process (AHP) Tool showcase 2"},{"image":"/img/ahp-new-1.gif","description":"-","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Analytic Hierarchy Process (AHP) Tool showcase 3"},{"image":"/img/ahp-new-2.gif","description":"-","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Analytic Hierarchy Process (AHP) Tool showcase 4"}],"image":"/img/ahp-new-2.png","description":"## Overview\n\n# Project Overview\n\nThe Analytic Hierarchy Process (AHP) app is a Vite + React single-page application that helps teams create, edit, and review decision analyses directly in the browser. It ships with sample scenarios, includes an admin-only dashboard, and operates entirely on client-side storage so it can run on static hosting without server dependencies.\n\n## Key capabilities\n- Admin authentication gate protecting the dashboard and editing workflows.\n- Creation and editing of AHP analyses with persisted data in the browser.\n- Dashboard overview cards, recent analysis lists, and quick links into edit flows.\n- Responsive UI composed from Tailwind CSS utilities, Radix UI primitives, and Shadcn-inspired components.\n- Client-only data layer that seeds from JSON and syncs via `localStorage` (with an in-memory fallback for non-browser contexts).\n\n## High-level architecture\n- **React SPA with hash-based routing:** Uses `react-router-dom` v7 hash router so routes work on static hosts without custom rewrite rules.\n- **Feature organization:** Routed pages live in `src/pages`, shared UI lives in `src/components` (with `components/ui` for primitives), and shared logic resides in `src/hooks` and `src/utils`.\n- **State and persistence:** Authentication context tracks login status in `localStorage`, while the AHP data service provides CRUD operations backed by local storage and emits change events to keep views synchronized.\n- **Styling and visuals:** Tailwind CSS powers layout and spacing, Radix UI primitives supply accessibility, and Recharts renders anal
94ytics visualizations.\n\n## Included libraries (high level)\n- React ecosystem: `react`, `react-dom`, `react-router-dom`\n- Styling \u0026amp; UI: Tailwind CSS, Radix UI packages, `tailwind-merge`, `clsx`, `lucide-react`, `framer-motion`, `tailwindcss-animate`, `react-resizable-panels`, `embla-carousel-react`, `react-day-picker`, `cmdk`, `sonner`\n- Forms \u0026amp; validation: `react-hook-form`, `zod`, `@hookform/resolvers`\n- Data visualization: `recharts`\n- Utilities and helpers: `date-fns`, `class-variance-authority`, `input-otp`, `vaul`, `next-themes`, `prop-types`\n\n## Technical Details\n\n# Technical Details\n\n## Application structure\n- **Entry point:** `src/main.jsx` mounts the React tree and global providers (e.g., toaster) before rendering the router defined in `src/pages/index.jsx`.\n- **Routing:** Uses `createHashRouter` from `react-router-dom` v7. `AuthProvider` wraps the router, and `RequireAuth` protects all routes except `/login`.\n- **Layout shell:** `src/pages/Layout.jsx` provides the global navigation, header, and content container shared by routed pages.\n- **Pages:**\n - `Dashboard.jsx` renders overview cards (`StatsOverview`), recent analyses (`RecentAnalyses`), and actions to view/edit/delete analyses.\n - `CreateAnalysis.jsx` supplies the form-driven experience for building or editing AHP models, using validation helpers and shared UI primitives.\n - `Login.jsx` presents the admin authentication form.\n\n## Authentication flow\n- Implemented in `src/hooks/useAuth.jsx` via a React context and `useAuth` hook.\n- Credentials come from `VITE_ADMIN_USERNAME` and `VITE_ADMIN_PASSWORD` with safe defaults for local development.\n- Auth state persists in `localStorage` under `ahp:auth:v1`; a constant-time password comparison reduces timing-leak risk.\n- Protected routes redirect unauthenticated users to `/login`, and the context exposes `login`/`logout` helpers for UI components.\n\n## Data layer and APIs\n- Seed data lives in `src/data/analyses.json` and is loaded on first run.\n- `src/api/entities.js` exports `DecisionAnalysis`, a service exposing CRUD operations against the stored analyses:\n - `list(orderBy)` sorts results (e.g., `-created_date`).\n - `get(id)` retrieves a specific analysis or throws if missing.\n - `create(payload)` inserts a new analysis, generating IDs and timestamps as needed.\n - `update(id, updates)` merges changes and refreshes the `updated_date` stamp.\n - `delete(id)` removes an analysis by ID.\n- Storage writes to `localStorage` when available, with an in-memory fallback for environments without persistent browser storage.\n- After mutations, the service dispatches a custom `analyses_changed` DOM event so dashboard and list views can refresh automatically.\n\n## Styling and UI system\n- Tailwind CSS provides utility-first styling; `clsx` and `tailwind-merge` help compose class names safely.\n- Radix UI primitives and Shadcn-inspired components live under `src/components/ui`, covering dialogs, popovers, dropdowns, and other interactive elements.\n- Icons use `lucide-react`; motion and animation rely on `framer-motion` and `tailwindcss-animate`.\n- Charts and summaries on the dashboard use `recharts` for data visualization.\n\n## Utilities and helpers\n- `src/lib/utils.js` offers the `cn` helper for class name merging.\n- `src/utils` contains routing helpers (`createPageUrl`, `PAGE_ROUTES`) and other shared logic reused across pages and components.\n\n## Testing and quality\n- ESLint configuration lives in `eslint.config.js` and runs via `npm run lint` to enforce consistency across React and hook usage.\n- The project currently relies on manual and exploratory testing alongside linting; no automated test suite is configured in the repository.\n\n- ChatOps interface lets teams approve or override automated fixes.\n- Pluggable policy hooks support vendor-specific add-ons.","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n h2: \"h2\",\n h1: \"h1\",\n p: \"p\",\n ul: \"ul\",\n li: \"li\",\n code: \"code\",\n strong: \"strong\"\n }, _provideComponents(), props.components);\n return _jsxs(_Fragment, {\n children: [_jsx(_components.h2, {\n children: \"Overview\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Project Overview\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"The Analytic Hierarchy Process (AHP) app is a Vite + React single-page application that helps teams create, edit, and review decision analyses directly in the browser. It ships with sample scenarios, includes an admin-only dashboard, and operates entirely on client-side storage so it can run on static hosting without server dependencies.\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Key capabilities\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"Admin authentication gate protecting the dashboard and editing workflows.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Creation and editing of AHP analyses with persisted data in the browser.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Dashboard overview cards, recent analysis lists, and quick links into edit flows.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Responsive UI composed from Tailwind CSS utilities, Radix UI primitives, and Shadcn-inspired components.\"\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Client-only data layer that seeds from JSON and syncs via \", _jsx(_components.c
94ode, {\n children: \"localStorage\"\n }), \" (with an in-memory fallback for non-browser contexts).\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"High-level architecture\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"React SPA with hash-based routing:\"\n }), \" Uses \", _jsx(_components.code, {\n children: \"react-router-dom\"\n }), \" v7 hash router so routes work on static hosts without custom rewrite rules.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Feature organization:\"\n }), \" Routed pages live in \", _jsx(_components.code, {\n children: \"src/pages\"\n }), \", shared UI lives in \", _jsx(_components.code, {\n children: \"src/components\"\n }), \" (with \", _jsx(_components.code, {\n children: \"components/ui\"\n }), \" for primitives), and shared logic resides in \", _jsx(_components.code, {\n children: \"src/hooks\"\n }), \" and \", _jsx(_components.code, {\n children: \"src/utils\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"State and persistence:\"\n }), \" Authentication context tracks login status in \", _jsx(_components.code, {\n children: \"localStorage\"\n }), \", while the AHP data service provides CRUD operations backed by local storage and emits change events to keep views synchronized.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Styling and visuals:\"\n }), \" Tailwind CSS powers layout and spacing, Radix UI primitives supply accessibility, and Recharts renders analytics visualizations.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Included libraries (high level)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"React ecosystem: \", _jsx(_components.code, {\n children: \"react\"\n }), \", \", _jsx(_components.code, {\n children: \"react-dom\"\n }), \", \", _jsx(_components.code, {\n children: \"react-router-dom\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Styling \u0026 UI: Tailwind CSS, Radix UI packages, \", _jsx(_components.code, {\n children: \"tailwind-merge\"\n }), \", \", _jsx(_components.code, {\n children: \"clsx\"\n }), \", \", _jsx(_components.code, {\n children: \"lucide-react\"\n }), \", \", _jsx(_components.code, {\n children: \"framer-motion\"\n }), \", \", _jsx(_components.code, {\n children: \"tailwindcss-animate\"\n }), \", \", _jsx(_components.code, {\n children: \"react-resizable-panels\"\n }), \", \", _jsx(_components.code, {\n children: \"embla-carousel-react\"\n }), \", \", _jsx(_components.code, {\n children: \"react-day-picker\"\n }), \", \", _jsx(_components.code, {\n children: \"cmdk\"\n }), \", \", _jsx(_components.code, {\n children: \"sonner\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Forms \u0026 validation: \", _jsx(_components.code, {\n children: \"react-hook-form\"\n }), \", \", _jsx(_components.code, {\n children: \"zod\"\n }), \", \", _jsx(_components.code, {\n children: \"@hookform/resolvers\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Data visualization: \", _jsx(_components.code, {\n children: \"recharts\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Utilities and helpers: \", _jsx(_components.code, {\n children: \"date-fns\"\n }
94), \", \", _jsx(_components.code, {\n children: \"class-variance-authority\"\n }), \", \", _jsx(_components.code, {\n children: \"input-otp\"\n }), \", \", _jsx(_components.code, {\n children: \"vaul\"\n }), \", \", _jsx(_components.code, {\n children: \"next-themes\"\n }), \", \", _jsx(_components.code, {\n children: \"prop-types\"\n })]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Technical Details\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Technical Details\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Application structure\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Entry point:\"\n }), \" \", _jsx(_components.code, {\n children: \"src/main.jsx\"\n }), \" mounts the React tree and global providers (e.g., toaster) before rendering the router defined in \", _jsx(_components.code, {\n children: \"src/pages/index.jsx\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Routing:\"\n }), \" Uses \", _jsx(_components.code, {\n children: \"createHashRouter\"\n }), \" from \", _jsx(_components.code, {\n children: \"react-router-dom\"\n }), \" v7. \", _jsx(_components.code, {\n children: \"AuthProvider\"\n }), \" wraps the router, and \", _jsx(_components.code, {\n children: \"RequireAuth\"\n }), \" protects all routes except \", _jsx(_components.code, {\n children: \"/login\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Layout shell:\"\n }), \" \", _jsx(_components.code, {\n children: \"src/pages/Layout.jsx\"\n }), \" provides the global navigation, header, and content container shared by routed pages.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Pages:\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"Dashboard.jsx\"\n }), \" renders overview cards (\", _jsx(_components.code, {\n children: \"StatsOverview\"\n }), \"), recent analyses (\", _jsx(_components.code, {\n children: \"RecentAnalyses\"\n }), \"), and actions to view/edit/delete analyses.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"CreateAnalysis.jsx\"\n }), \" supplies the form-driven experience for building or editing AHP models, using validation helpers and shared UI primitives.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"Login.jsx\"\n }), \" presents the admin authentication form.\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Authentication flow\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Implemented in \", _jsx(_components.code, {\n children: \"src/hooks/useAuth.jsx\"\n }), \" via a React context and \", _jsx(_components.code, {\n children: \"useAuth\"\n }), \" hook.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Credentials come from \", _jsx(_components.code, {\n children: \"VITE_ADMIN_USERNAME\"\n }), \" and \", _jsx(_components.code, {\n children: \"VITE_ADMIN_PASSWORD\"\n }), \" with safe defaults for local development.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Auth state persists in \", _jsx(_components.c
94ode, {\n children: \"localStorage\"\n }), \" under \", _jsx(_components.code, {\n children: \"ahp:auth:v1\"\n }), \"; a constant-time password comparison reduces timing-leak risk.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Protected routes redirect unauthenticated users to \", _jsx(_components.code, {\n children: \"/login\"\n }), \", and the context exposes \", _jsx(_components.code, {\n children: \"login\"\n }), \"/\", _jsx(_components.code, {\n children: \"logout\"\n }), \" helpers for UI components.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Data layer and APIs\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Seed data lives in \", _jsx(_components.code, {\n children: \"src/data/analyses.json\"\n }), \" and is loaded on first run.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"src/api/entities.js\"\n }), \" exports \", _jsx(_components.code, {\n children: \"DecisionAnalysis\"\n }), \", a service exposing CRUD operations against the stored analyses:\", \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"list(orderBy)\"\n }), \" sorts results (e.g., \", _jsx(_components.code, {\n children: \"-created_date\"\n }), \").\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"get(id)\"\n }), \" retrieves a specific analysis or throws if missing.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"create(payload)\"\n }), \" inserts a new analysis, generating IDs and timestamps as needed.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"update(id, updates)\"\n }), \" merges changes and refreshes the \", _jsx(_components.code, {\n children: \"updated_date\"\n }), \" stamp.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"delete(id)\"\n }), \" removes an analysis by ID.\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Storage writes to \", _jsx(_components.code, {\n children: \"localStorage\"\n }), \" when available, with an in-memory fallback for environments without persistent browser storage.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"After mutations, the service dispatches a custom \", _jsx(_components.code, {\n children: \"analyses_changed\"\n }), \" DOM event so dashboard and list views can refresh automatically.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Styling and UI system\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Tailwind CSS provides utility-first styling; \", _jsx(_components.code, {\n children: \"clsx\"\n }), \" and \", _jsx(_components.code, {\n children: \"tailwind-merge\"\n }), \" help compose class names safely.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Radix UI primitives and Shadcn-inspired components live under \", _jsx(_components.code, {\n children: \"src/components/ui\"\n }), \", covering dialogs, popovers, dropdowns, and other interactive elements.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Icons use \", _jsx(_components.code, {\n children: \"lucide-react\"\n }), \"; motion and animation rely on \", _jsx(_components.code, {\n children: \"framer-motion\"\n }
94), \" and \", _jsx(_components.code, {\n children: \"tailwindcss-animate\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Charts and summaries on the dashboard use \", _jsx(_components.code, {\n children: \"recharts\"\n }), \" for data visualization.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Utilities and helpers\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"src/lib/utils.js\"\n }), \" offers the \", _jsx(_components.code, {\n children: \"cn\"\n }), \" helper for class name merging.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"src/utils\"\n }), \" contains routing helpers (\", _jsx(_components.code, {\n children: \"createPageUrl\"\n }), \", \", _jsx(_components.code, {\n children: \"PAGE_ROUTES\"\n }), \") and other shared logic reused across pages and components.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Testing and quality\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [\"ESLint configuration lives in \", _jsx(_components.code, {\n children: \"eslint.config.js\"\n }), \" and runs via \", _jsx(_components.code, {\n children: \"npm run lint\"\n }), \" to enforce consistency across React and hook usage.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"The project currently relies on manual and exploratory testing alongside linting; no automated test suite is configured in the repository.\"\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"ChatOps interface lets teams approve or override automated fixes.\"\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"Pluggable policy hooks support vendor-specific add-ons.\"\n }), \"\\n\"]\n }), \"\\n\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"requirements":"---\ntitle: \"AHP Functional Requirements\"\nversion: \"1.0\"\n---\n\n# Overview\nThe application is a single-page Analytic Hierarchy Process (AHP) assistant built with Vite and React. It guides an authenticated administrator through defining decision problems, performing pairwise comparisons for criteria and alternatives, calculating priorities and consistency ratios, and reviewing/exporting results. All decision data is stored locally in the browser (or an in-memory fallback) using seeded sample analyses as starting content.\n\n# Scope\n- **In scope**: Admin authentication, decision analysis CRUD, step-by-step AHP workflow (setup â criteria â alternatives â comparisons â results), consistency analysis, dashboard summaries, local data persistence, and CSV export of results.\n- **Out of scope**: Multi-user collaboration, server-side persistence, real-time networking, external API integrations, and role/permission models beyond a single admin user.\n\n# Functional Requirements\n\n## FR-1: Administrator Authentication\n- **Goal**: Restrict access to the dashboard and analysis tools to authenticated administrators.\n- **Trigger**: A visitor navigates to any protected route without an active session.\n- **Preconditions**: Browser supports JavaScript; admin credentials configured via environment variables or defaults.\n- **Expected Behavior**: Redirect to the login page, display read-only username, validate submitted password using constant-time comparison, and persist authenticated state to localStorage. Successful login redirects back to the originally requested route; failures show an error message.\n- **Postconditions**: Auth state cached under `ahp:auth:v1`; layout shows user initials and enables protected routes.\n- **Exceptions / Edge Cases**: Default password fallback triggers a security notice; localStorage failures silently degrade without breaking flow.\n\n## FR-2: Dashboard Overview\n- **Goal**: Present recent analyses and ke
94y metrics after authentication.\n- **Trigger**: Authenticated user opens the dashboard route.\n- **Preconditions**: Analyses available in local storage or seeded defaults.\n- **Expected Behavior**: Load and optionally sort analyses, compute totals, completed/in-progress counts, and average criteria consistency. Render stats cards, quick tips, and a list of up to five recent analyses with status badges, metadata, and actions to review or delete.\n- **Postconditions**: Sidebar stats (total/this month) reflect current data; deletion updates store and emits change event.\n- **Exceptions / Edge Cases**: Empty states show guidance; loading state shows skeletons; errors logged to console.\n\n## FR-3: Create or Edit Analysis Wizard\n- **Goal**: Guide administrators through creating or updating a decision analysis via ordered steps.\n- **Trigger**: User selects âNew Analysisâ or âReviewâ from dashboard (optionally with an analysis id in query params).\n- **Preconditions**: Authenticated session; local data store available.\n- **Expected Behavior**: Initialize with defaults or load existing analysis (including last step index). Enforce step order with navigation controls and persist status (`setup`, `criteria_defined`, `alternatives_defined`, `criteria_compared`, `completed`) and last visited step index. Autosave changes when title/goal provided and data differs from last snapshot.\n- **Postconditions**: Saved analyses include timestamps, persisted data, and updated status; completion redirects to dashboard.\n- **Exceptions / Edge Cases**: Loading state shown while fetching; navigating to missing id redirects to dashboard; autosave suppressed during explicit navigation actions.\n\n## FR-4: Criteria Management\n- **Goal**: Capture the decision criteria set.\n- **Trigger**: User proceeds to the Criteria step.\n- **Preconditions**: Title and goal provided.\n- **Expected Behavior**: Display default seed criteria; allow adding criteria with unique names (case-insensitive) and optional descriptions, up to 20 total. Removal is supported. Must have between 2 and 20 criteria to continue; status updates to `criteria_defined` when criteria added.\n- **Postconditions**: Criteria list stored in analysis state and persisted on save/autosave.\n- **Exceptions / Edge Cases**: Duplicate names rejected with a destructive toast; empty list shows placeholder guidance.\n\n## FR-5: Alternatives Management\n- **Goal**: Capture decision alternatives.\n- **Trigger**: User proceeds to the Alternatives step.\n- **Preconditions**: At least two criteria defined.\n- **Expected Behavior**: Add alternatives with unique names (case-insensitive), optional descriptions, up to 10 total. Must maintain 2â10 alternatives to advance; status updates to `alternatives_defined` when alternatives added. Shows decision goal context.\n- **Postconditions**: Alternatives list saved with analysis data.\n- **Exceptions / Edge Cases**: Duplicate names rejected via toast; empty state messaging; progression blocked if outside count bounds.\n\n## FR-6: Criteria Pairwise Comparisons\n- **Goal**: Collect pairwise importance judgments among criteria using AHP scale.\n- **Trigger**: User enters the âCompare Criteriaâ step.\n- **Preconditions**: At least two criteria and alternatives defined.\n- **Expected Behavior**: Generate all unique criterion pairs, prepopulate with normalized stored values or defaults, and present slider (17-point mapping to 1/9â9 scale) with descriptive text. Track progress and live consistency ratio when â¥50% comparisons done and â¥3 criteria. Compute weights via normalized column sums and derive consistency ratio for saving. Status updates to `criteria_compared` upon completion.\n- **Postconditions**: Persisted `criteria_comparisons`, `criteria_weights`, `criteria_consistency_ratio`, and updated status.\n- **Exceptions / Edge Cases**: Reverse/legacy comparison values normalized; warnings shown when live consistency \u003e10%; disables completion until all comparisons entered; handles absence of pairs.\n\n## FR-7: Alternative Comparisons per Criterion\n- **Goal**: Evaluate alternatives pairwise for each criterion.\n- **Trigger**: User enters the âCompare Alternativesâ step.\n- **Preconditions**: Criteria weights available; at least two alternatives.\n- **Expected Behavior**: For each criterion, generate all alternative pairs with per-criterion comparison maps using the same AHP scale. Track per-criterion and overall progress, compute per-criterion consistency when â¥50% comparisons exist and â¥3 alternatives, and summarize current consistency badge. Finalize by computing local weights, criterion-weighted alternative scores, and per-criterion consistency ratios; set status to `completed`.\n- **Postconditions**: Persisted `alternative_comparisons`, `alternative_scores`, `alternative_consistency_ratios`, status `completed`, and last step index updated.\n- **Exceptions / Edge Cases**: Blocks final save until all comparisons re
94corded; handles missing pairs; resets consistency indicator when changing criterion.\n\n## FR-8: Results Visualization \u0026amp; Export\n- **Goal**: Present final rankings, weight breakdowns, and consistency diagnostics with export support.\n- **Trigger**: User completes comparisons or loads a completed analysis.\n- **Preconditions**: Criteria weights and alternative scores computed.\n- **Expected Behavior**: Display winner with confidence badge (high if criteria CR \u0026lt;10% and average alternative CR \u0026lt;10%), bar charts for rankings and criteria weights, detailed tables with local and weighted contributions, and consistency summary/detail cards. Offer CSV export containing metadata, rankings, consistency metrics, and matrix values. âSaveâ/âBack to Dashboardâ button persists analysis and navigates appropriately.\n- **Postconditions**: Exported CSV downloaded via browser; user can return to dashboard.\n- **Exceptions / Edge Cases**: Handles missing data with zeroed weights/scores; safe division guards against invalid numbers.\n\n## FR-9: Analysis Lifecycle Management\n- **Goal**: Persist, update, and delete analyses locally.\n- **Trigger**: Any create/update/delete action from wizard or dashboard.\n- **Preconditions**: Local storage accessible or in-memory fallback available.\n- **Expected Behavior**: CRUD operations update in-memory store and browser localStorage (`ahp_decision_analyses`), assigning unique IDs and timestamps. Emission of `analyses_changed` event keeps UI in sync. Sorting supports field/direction, including date handling.\n- **Postconditions**: Local store reflects latest state; sidebar stats auto-refresh on change events.\n- **Exceptions / Edge Cases**: Invalid IDs throw errors on get/delete/update; parse failures reset to defaults with warning.\n\n# System Components\n- **Router \u0026amp; Pages** (`src/pages/index.jsx`): Hash-based routing with protected routes, login, dashboard, and wizard pages wrapped in `AuthProvider`.\n- **Auth Context** (`src/hooks/useAuth.jsx`): Manages authentication state, credential validation, persistence, and exposes login/logout helpers.\n- **Data Access Layer** (`src/api/entities.js`): Local persistence for decision analyses with change event dispatch and sorting utilities.\n- **Layout \u0026amp; Navigation** (`src/pages/Layout.jsx`): Sidebar with navigation, quick stats, user panel, and responsive header.\n- **Dashboard Widgets** (`src/components/dashboard/*`): Stats cards, recent analyses list with actions, and quick tips.\n- **Analysis Wizard Components** (`src/components/analysis/*`): Step-specific UIs for setup, criteria, alternatives, comparisons, results, consistency analysis, and progress stepper.\n- **Utility Modules** (`src/utils/ahp-scale.js`, `src/utils/index.js`): AHP scale mapping, normalization, comparison descriptions, and route helper.\n\n# Data Structures \u0026amp; Models\n- **DecisionAnalysis**\n - `id`: unique string (`analysis_\u003ctimestamp\u003e_\u003crandom\u003e` default)\n - `title`, `description`, `goal`: strings\n - `criteria`: array of `{ id, name, description? }`\n - `alternatives`: array of `{ id, name, description? }`\n - `criteria_comparisons`: map `\"criterionA-criterionB\" -\u003e numeric scale value`\n - `alternative_comparisons`: map `criterionId -\u003e { \"altA-altB\": value }`\n - `criteria_weights`: map `criterionId -\u003e weight (0-1)`\n - `alternative_scores`: map `alternativeId -\u003e final weighted score (0-1)`\n - `criteria_consistency_ratio`: number (0-1)\n - `alternative_consistency_ratios`: map `criterionId -\u003e consistency ratio`\n - `status`: lifecycle string (`setup` | `criteria_defined` | `alternatives_defined` | `criteria_compared` | `completed` | `in_progress`)\n - `last_step_index`: number indicating wizard progress\n - `created_date`, `updated_date`: ISO strings\n- **Auth State**\n - `isAuthenticated`: boolean\n - `user`: `{ username, name, role }`\n - Stored in localStorage under `ahp:auth:v1`.\n- **Events**\n - `analyses_changed`: CustomEvent dispatched on CRUD to refresh listeners.\n\n# API / Interface Behavior\
94n- **DecisionAnalysis.list(orderBy?)**: Returns analyses sorted by optional field (`-field` for desc); reads from local store.\n- **DecisionAnalysis.get(id)**: Retrieves a single analysis or throws if missing.\n- **DecisionAnalysis.create(payload)**: Clones payload, assigns id/timestamps, prepends to store, persists, emits change.\n- **DecisionAnalysis.update(id, updates)**: Merges updates into matching analysis with new timestamp; persists and emits change or throws if not found.\n- **DecisionAnalysis.delete(id)**: Removes analysis by id; persists and emits change or throws on missing id.\n- **Auth.login(password)**: Validates password against env/default using constant-time comparison; on success sets authenticated state and persists.\n- **Auth.logout()**: Clears auth state and storage.\n- **Route helper createPageUrl(name, query?)**: Builds path with optional query string parameters.\n- **AHP Scale Helpers**: Convert between slider values and comparison ratios, normalize legacy values, invert comparisons, and produce human-readable descriptions.\n\n# Workflows\n- **Login Flow**: Unauthenticated access redirects to `/login` â user submits password â on success, auth stored and user redirected to original route; errors shown otherwise.\n- **Analysis Creation**: Start new analysis â fill setup fields â add criteria (2â20) â add alternatives (2â10) â perform criterion comparisons â perform per-criterion alternative comparisons â review results â save/export; autosave occurs when valid title/goal and changes detected.\n- **Editing Existing Analysis**: Open dashboard â choose âReviewâ on an analysis â wizard loads saved data, resumes at `last_step_index` (or results if c
94ompleted) â user adjusts steps â saves draft or final.\n- **Deletion**: From dashboard list, confirm deletion â remove analysis from store â toast notification â UI refresh via state update and change event.\n- **CSV Export**: From results view, generate CSV with analysis metadata, rankings, consistency metrics, and matrices â trigger browser download with sanitized filename.\n\n# Assumptions \u0026amp; Constraints\n- Single admin user model; no multi-user/session synchronization.\n- Client-side only; relies on browser localStorage with in-memory fallback for non-browser contexts.\n- AHP calculations use simplified eigenvector approximations and fixed Random Index values; acceptable CR threshold set at 10% for guidance.\n- Comparisons require at least half completion before live consistency is shown; meaningful CR requires â¥3 items.\n- Wizard navigation restricted to sequential steps; cannot progress without meeting minimum counts/required fields.\n\n# Missing or Ambiguous Requirements\n- No explicit requirement for password complexity or rotation beyond environment override guidance.\n- Error handling for localStorage failures is minimal (console warnings); user-facing behavior under storage quota issues is unspecified.\n- Handling of concurrent edits across browser tabs is undefined; last write wins silently.\n- Maximum limits for criteria/alternatives are coded but not justified; business rationale and configurability are unclear.\n- Accessibility expectations (beyond general UI) and internationalization/localization requirements are not specified.\n- Precision/rounding rules for weights and scores in exports and UI are implicit; acceptable tolerances need confirmation.","requirementsMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n hr: \"hr\",\n h2: \"h2\",\n h1: \"h1\",\n p: \"p\",\n ul: \"ul\",\n li: \"li\",\n strong: \"strong\",\n code: \"code\"\n }, _provideComponents(), props.components);\n return _jsxs(_Fragment, {\n children: [_jsx(_components.hr, {}), \"\\n\", _jsx(_components.h2, {\n children: \"title: \\\"AHP Functional Requirements\\\"\\nversion: \\\"1.0\\\"\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Overview\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"The application is a single-page Analytic Hierarchy Process (AHP) assistant built with Vite and React. It guides an authenticated administrator through defining decision problems, performing pairwise comparisons for criteria and alternatives, calculating priorities and consistency ratios, and reviewing/exporting results. All decision data is stored locally in the browser (or an in-memory fallback) using seeded sample analyses as starting content.\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Scope\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"In scope\"\n }), \": Admin authentication, decision analysis CRUD, step-by-step AHP workflow (setup â criteria â alternatives â comparisons â results), consistency analysis, dashboard summaries, local data persistence, and CSV export of results.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Out of scope\"\n }), \": Multi-user collaboration, server-side persistence, real-time networking, external API integrations, and role/permission models beyond a single admin user.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Functional Requirements\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"FR-1: Administrator Authentication\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Goal\"\n }), \": Restrict access to the dashboard and analysis tools to authenticated administrators.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Trigger\"\n }), \": A visitor navigates to any protected route without an active session.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Preconditions\"\n }), \": Browser supports JavaScript; admin credentials configured via environment variables or defaults.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Expected Behavior\"\n }), \": Redirect to the login page, display read-only username, validate submitted password using constant-time comparison, and persist authenticated state to localStorage. Successful login redirects back to the originally requested route; failures show an error message.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Postconditions\"\n }
94), \": Auth state cached under \", _jsx(_components.code, {\n children: \"ahp:auth:v1\"\n }), \"; layout shows user initials and enables protected routes.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Exceptions / Edge Cases\"\n }), \": Default password fallback triggers a security notice; localStorage failures silently degrade without breaking flow.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"FR-2: Dashboard Overview\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Goal\"\n }), \": Present recent analyses and key metrics after authentication.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Trigger\"\n }), \": Authenticated user opens the dashboard route.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Preconditions\"\n }), \": Analyses available in local storage or seeded defaults.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Expected Behavior\"\n }), \": Load and optionally sort analyses, compute totals, completed/in-progress counts, and average criteria consistency. Render stats cards, quick tips, and a list of up to five recent analyses with status badges, metadata, and actions to review or delete.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Postconditions\"\n }), \": Sidebar stats (total/this month) reflect current data; deletion updates store and emits change event.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Exceptions / Edge Cases\"\n }), \": Empty states show guidance; loading state shows skeletons; errors logged to console.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"FR-3: Create or Edit Analysis Wizard\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Goal\"\n }), \": Guide administrators through creating or updating a decision analysis via ordered steps.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Trigger\"\n }), \": User selects âNew Analysisâ or âReviewâ from dashboard (optionally with an analysis id in query params).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Preconditions\"\n }), \": Authenticated session; local data store available.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Expected Behavior\"\n }), \": Initialize with defaults or load existing analysis (including last step index). Enforce step order with navigation controls and persist status (\", _jsx(_components.code, {\n children: \"setup\"\n }), \", \", _jsx(_components.code, {\n children: \"criteria_defined\"\n }), \", \", _jsx(_components.code, {\n children: \"alternatives_defined\"\n }), \", \", _jsx(_components.code, {\n children: \"criteria_compared\"\n }), \", \", _jsx(_components.code, {\n children: \"completed\"\n }), \") and last visited step index. Autosave changes when title/goal provided and data differs from last snapshot.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Postconditions\"\n }), \": Saved analyses include timestamps, persisted data, and updated status; completion redirects to dashboard.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Exceptions / Edge Cases\"\n }), \": Loading state shown while fetching; navigating to missing id redirects to dashboard; autosave suppressed during explicit navigation actions.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"FR-4: Criteria Management\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Goal\"\n }), \": Capture the decision criteria set.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Trigger\"\n }), \": User proceeds to the Criteria step.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Preconditions\"\n }), \": Title and goal provided.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Expected Behavior\"\n }), \": Display default seed criteria; allow adding criteria with unique names (case-insensitive) and optional descriptions, up to 20 total. Removal is supported. Must have between 2 and 20 criteria to continue;
94 status updates to \", _jsx(_components.code, {\n children: \"criteria_defined\"\n }), \" when criteria added.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Postconditions\"\n }), \": Criteria list stored in analysis state and persisted on save/autosave.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Exceptions / Edge Cases\"\n }), \": Duplicate names rejected with a destructive toast; empty list shows placeholder guidance.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"FR-5: Alternatives Management\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Goal\"\n }), \": Capture decision alternatives.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Trigger\"\n }), \": User proceeds to the Alternatives step.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Preconditions\"\n }), \": At least two criteria defined.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Expected Behavior\"\n }), \": Add alternatives with unique names (case-insensitive), optional descriptions, up to 10 total. Must maintain 2â10 alternatives to advance; status updates to \", _jsx(_components.code, {\n children: \"alternatives_defined\"\n }), \" when alternatives added. Shows decision goal context.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Postconditions\"\n }), \": Alternatives list saved with analysis data.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Exceptions / Edge Cases\"\n }), \": Duplicate names rejected via toast; empty state messaging; progression blocked if outside count bounds.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"FR-6: Criteria Pairwise Comparisons\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Goal\"\n }), \": Collect pairwise importance judgments among criteria using AHP scale.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Trigger\"\n }), \": User enters the âCompare Criteriaâ step.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Preconditions\"\n }), \": At least two criteria and alternatives defined.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Expected Behavior\"\n }), \": Generate all unique criterion pairs, prepopulate with normalized stored values or defaults, and present slider (17-point mapping to 1/9â9 scale) with descriptive text. Track progress and live consistency ratio when â¥50% comparisons done and â¥3 criteria. Compute weights via normalized column sums and derive consistency ratio for saving. Status updates to \", _jsx(_components.code, {\n children: \"criteria_compared\"\n }), \" upon completion.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Postconditions\"\n }), \": Persisted \", _jsx(_components.code, {\n children: \"criteria_comparisons\"\n }), \", \", _jsx(_components.code, {\n children: \"criteria_weights\"\n }), \", \", _jsx(_components.code, {\n children: \"criteria_consistency_ratio\"\n }), \", and updated status.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Exceptions / Edge Cases\"\n }
94), \": Reverse/legacy comparison values normalized; warnings shown when live consistency \u003e10%; disables completion until all comparisons entered; handles absence of pairs.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"FR-7: Alternative Comparisons per Criterion\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Goal\"\n }), \": Evaluate alternatives pairwise for each criterion.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Trigger\"\n }), \": User enters the âCompare Alternativesâ step.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Preconditions\"\n }), \": Criteria weights available; at least two alternatives.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Expected Behavior\"\n }), \": For each criterion, generate all alternative pairs with per-criterion comparison maps using the same AHP scale. Track per-criterion and overall progress, compute per-criterion consistency when â¥50% comparisons exist and â¥3 alternatives, and summarize current consistency badge. Finalize by computing local weights, criterion-weighted alternative scores, and per-criterion consistency ratios; set status to \", _jsx(_components.code, {\n children: \"completed\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Postconditions\"\n }), \": Persisted \", _jsx(_components.code, {\n children: \"alternative_comparisons\"\n }), \", \", _jsx(_components.code, {\n children: \"alternative_scores\"\n }), \", \", _jsx(_components.code, {\n children: \"alternative_consistency_ratios\"\n }), \", status \", _jsx(_components.code, {\n children: \"completed\"\n }), \", and last step index updated.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Exceptions / Edge Cases\"\n }), \": Blocks final save until all comparisons recorded; handles missing pairs; resets consistency indicator when changing criterion.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"FR-8: Results Visualization \u0026 Export\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Goal\"\n }), \": Present final rankings, weight breakdowns, and consistency diagnostics with export support.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Trigger\"\n }), \": User completes comparisons or loads a completed analysis.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Preconditions\"\n }), \": Criteria weights and alternative scores computed.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Expected Behavior\"\n }), \": Display winner with confidence badge (high if criteria CR \u003c10% and average alternative CR \u003c10%), bar charts for rankings and criteria weights, detailed tables with local and weighted contributions, and consistency summary/detail cards. Offer CSV export containing metadata, rankings, consistency metrics, and matrix values. âSaveâ/âBack to Dashboardâ button persists analysis and navigates appropriately.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Postconditions\"\n }), \": Exported CSV downloaded via browser; user can return to dashboard.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Exceptions / Edge Cases\"\n }), \": Handles missing data with zeroed weights/scores; safe division guards against invalid numbers.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"FR-9: Analysis Lifecycle Management\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Goal\"\n }), \": Persist, update, and delete analyses locally.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Trigger\"\n }), \": Any create/update/delete action from wizard or dashboard.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Preconditions\"\n }), \": Local storage accessible or in-memory fallback available.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Expected Behavior\"\n }), \": CRUD operations update in-memory store and browser localStorage (\", _jsx(_components.c
94ode, {\n children: \"ahp_decision_analyses\"\n }), \"), assigning unique IDs and timestamps. Emission of \", _jsx(_components.code, {\n children: \"analyses_changed\"\n }), \" event keeps UI in sync. Sorting supports field/direction, including date handling.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Postconditions\"\n }), \": Local store reflects latest state; sidebar stats auto-refresh on change events.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Exceptions / Edge Cases\"\n }), \": Invalid IDs throw errors on get/delete/update; parse failures reset to defaults with warning.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h1, {\n children: \"System Components\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Router \u0026 Pages\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/pages/index.jsx\"\n }), \"): Hash-based routing with protected routes, login, dashboard, and wizard pages wrapped in \", _jsx(_components.code, {\n children: \"AuthProvider\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Auth Context\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/hooks/useAuth.jsx\"\n }), \"): Manages authentication state, credential validation, persistence, and exposes login/logout helpers.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Data Access Layer\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/api/entities.js\"\n }), \"): Local persistence for decision analyses with change event dispatch and sorting utilities.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Layout \u0026 Navigation\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/pages/Layout.jsx\"\n }), \"): Sidebar with navigation, quick stats, user panel, and responsive header.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Dashboard Widgets\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/components/dashboard/*\"\n }), \"): Stats cards, recent analyses list with actions, and quick tips.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Analysis Wizard Components\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/components/analysis/*\"\n }), \"): Step-specific UIs for setup, criteria, alternatives, comparisons, results, consistency analysis, and progress stepper.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Utility Modules\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/utils/ahp-scale.js\"\n }), \", \", _jsx(_components.code, {\n children: \"src/utils/index.js\"\n }), \"): AHP scale mapping, normalization, comparison descriptions, and route helper.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Data Structures \u0026 Models\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"DecisionAnalysis\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"id\"\n }), \": unique string (\", _jsx(_components.code, {\n children: \"analysis_\u003ctimestamp\u003e_\u003crandom\u003e\"\n }), \" default)\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.c
94ode, {\n children: \"title\"\n }), \", \", _jsx(_components.code, {\n children: \"description\"\n }), \", \", _jsx(_components.code, {\n children: \"goal\"\n }), \": strings\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"criteria\"\n }), \": array of \", _jsx(_components.code, {\n children: \"{ id, name, description? }\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"alternatives\"\n }), \": array of \", _jsx(_components.code, {\n children: \"{ id, name, description? }\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"criteria_comparisons\"\n }), \": map \", _jsx(_components.code, {\n children: \"\\\"criterionA-criterionB\\\" -\u003e numeric scale value\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"alternative_comparisons\"\n }), \": map \", _jsx(_components.code, {\n children: \"criterionId -\u003e { \\\"altA-altB\\\": value }\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"criteria_weights\"\n }), \": map \", _jsx(_components.code, {\n children: \"criterionId -\u003e weight (0-1)\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"alternative_scores\"\n }), \": map \", _jsx(_components.code, {\n children: \"alternativeId -\u003e final weighted score (0-1)\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"criteria_consistency_ratio\"\n }), \": number (0-1)\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"alternative_consistency_ratios\"\n }), \": map \", _jsx(_components.code, {\n children: \"criterionId -\u003e consistency ratio\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"status\"\n }), \": lifecycle string (\", _jsx(_components.code, {\n children: \"setup\"\n }), \" | \", _jsx(_components.code, {\n children: \"criteria_defined\"\n }), \" | \", _jsx(_components.code, {\n children: \"alternatives_defined\"\n }), \" | \", _jsx(_components.code, {\n children: \"criteria_compared\"\n }), \" | \", _jsx(_components.code, {\n children: \"completed\"\n }), \" | \", _jsx(_components.code, {\n children: \"in_progress\"\n }), \")\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"last_step_index\"\n }), \": number indicating wizard progress\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"created_date\"\n }), \", \", _jsx(_components.code, {\n children: \"updated_date\"\n }), \": ISO strings\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Auth State\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"isAuthenticated\"\n }), \": boolean\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.c
94ode, {\n children: \"user\"\n }), \": \", _jsx(_components.code, {\n children: \"{ username, name, role }\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Stored in localStorage under \", _jsx(_components.code, {\n children: \"ahp:auth:v1\"\n }), \".\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Events\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"analyses_changed\"\n }), \": CustomEvent dispatched on CRUD to refresh listeners.\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h1, {\n children: \"API / Interface Behavior\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"DecisionAnalysis.list(orderBy?)\"\n }), \": Returns analyses sorted by optional field (\", _jsx(_components.code, {\n children: \"-field\"\n }), \" for desc); reads from local store.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"DecisionAnalysis.get(id)\"\n }), \": Retrieves a single analysis or throws if missing.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"DecisionAnalysis.create(payload)\"\n }), \": Clones payload, assigns id/timestamps, prepends to store, persists, emits change.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"DecisionAnalysis.update(id, updates)\"\n }), \": Merges updates into matching analysis with new timestamp; persists and emits change or throws if not found.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"DecisionAnalysis.delete(id)\"\n }), \": Removes analysis by id; persists and emits change or throws on missing id.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Auth.login(password)\"\n }), \": Validates password against env/default using constant-time comparison; on success sets authenticated state and persists.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Auth.logout()\"\n }), \": Clears auth state and storage.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Route helper createPageUrl(name, query?)\"\n }), \": Builds path with optional query string parameters.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"AHP Scale Helpers\"\n }), \": Convert between slider values and comparison ratios, normalize legacy values, invert comparisons, and produce human-readable descriptions.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Workflows\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Login Flow\"\n }), \": Unauthenticated access redirects to \", _jsx(_components.code, {\n children: \"/login\"\n }), \" â user submits password â on success, auth stored and user redirected to original route; errors shown otherwise.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Analysis Creation\"\n }), \": Start new analysis â fill setup fields â add criteria (2â20) â add alternatives (2â10) â perform criterion comparisons â perform per-criterion alternative comparisons â review results â save/export; autosave occurs when valid title/goal and changes detected.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Editing Existing Analysis\"\n }), \": Open dashboard â choose âReviewâ on an analysis â wizard loads saved data, resumes at \", _jsx(_components.c
94ode, {\n children: \"last_step_index\"\n }), \" (or results if completed) â user adjusts steps â saves draft or final.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Deletion\"\n }), \": From dashboard list, confirm deletion â remove analysis from store â toast notification â UI refresh via state update and change event.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"CSV Export\"\n }), \": From results view, generate CSV with analysis metadata, rankings, consistency metrics, and matrices â trigger browser download with sanitized filename.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Assumptions \u0026 Constraints\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"Single admin user model; no multi-user/session synchronization.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Client-side only; relies on browser localStorage with in-memory fallback for non-browser contexts.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"AHP calculations use simplified eigenvector approximations and fixed Random Index values; acceptable CR threshold set at 10% for guidance.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Comparisons require at least half completion before live consistency is shown; meaningful CR requires â¥3 items.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Wizard navigation restricted to sequential steps; cannot progress without meeting minimum counts/required fields.\"\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Missing or Ambiguous Requirements\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"No explicit requirement for password complexity or rotation beyond environment override guidance.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Error handling for localStorage failures is minimal (console warnings); user-facing behavior under storage quota issues is unspecified.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Handling of concurrent edits across browser tabs is undefined; last write wins silently.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Maximum limits for criteria/alternatives are coded but not justified; business rationale and configurability are unclear.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Accessibility expectations (beyond general UI) and internationalization/localization requirements are not specified.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Precision/rounding rules for weights and scores in exports and UI are implicit; acceptable tolerances need confirmation.\"\n }), \"\\n\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"overviewTechnical":"## Overview\n\n# Project Overview\n\nThe Analytic Hierarchy Process (AHP) app is a Vite + React single-page application that helps teams create, edit, and review decision analyses directly in the browser. It ships with sample scenarios, includes an admin-only dashboard, and operates entirely on client-side storage so it can run on static hosting without server dependencies.\n\n## Key capabilities\n- Admin authentication gate protecting the dashboard and editing workflows.\n- Creation and editing of AHP analyses with persisted data in the browser.\n- Dashboard overview cards, recent analysis lists, and quick links into edit flows.\n- Responsive UI composed from Tailwind CSS utilities, Radix UI primitives, and Shadcn-inspired components.\n- Client-only data layer that seeds from JSON and syncs via `localStorage` (with an in-memory fallback for non-browser contexts).\n\n## High-level architecture\n- **React SPA with hash-based routing:** Uses `react-router-dom` v7 hash router so routes work on static hosts without custom rewrite rules.\n- **Feature organization:** Routed pages live in `src/pages`, shared UI lives in `src/components` (with `components/ui` for primitives), and shared logic resides in `src/hooks` and `src/utils`.\n- **State and persistence:** Authentication context tracks login status in `localStorage`, while the AHP data service provides CRUD operations backed by local storage and emits change events to keep views synchronized.\n- **Styling and visuals:** Tailwind CSS powers layout and spacing, Radix UI primitives supply accessibility, and Recharts renders anal
94ytics visualizations.\n\n## Included libraries (high level)\n- React ecosystem: `react`, `react-dom`, `react-router-dom`\n- Styling \u0026amp; UI: Tailwind CSS, Radix UI packages, `tailwind-merge`, `clsx`, `lucide-react`, `framer-motion`, `tailwindcss-animate`, `react-resizable-panels`, `embla-carousel-react`, `react-day-picker`, `cmdk`, `sonner`\n- Forms \u0026amp; validation: `react-hook-form`, `zod`, `@hookform/resolvers`\n- Data visualization: `recharts`\n- Utilities and helpers: `date-fns`, `class-variance-authority`, `input-otp`, `vaul`, `next-themes`, `prop-types`\n\n## Technical Details\n\n# Technical Details\n\n## Application structure\n- **Entry point:** `src/main.jsx` mounts the React tree and global providers (e.g., toaster) before rendering the router defined in `src/pages/index.jsx`.\n- **Routing:** Uses `createHashRouter` from `react-router-dom` v7. `AuthProvider` wraps the router, and `RequireAuth` protects all routes except `/login`.\n- **Layout shell:** `src/pages/Layout.jsx` provides the global navigation, header, and content container shared by routed pages.\n- **Pages:**\n - `Dashboard.jsx` renders overview cards (`StatsOverview`), recent analyses (`RecentAnalyses`), and actions to view/edit/delete analyses.\n - `CreateAnalysis.jsx` supplies the form-driven experience for building or editing AHP models, using validation helpers and shared UI primitives.\n - `Login.jsx` presents the admin authentication form.\n\n## Authentication flow\n- Implemented in `src/hooks/useAuth.jsx` via a React context and `useAuth` hook.\n- Credentials come from `VITE_ADMIN_USERNAME` and `VITE_ADMIN_PASSWORD` with safe defaults for local development.\n- Auth state persists in `localStorage` under `ahp:auth:v1`; a constant-time password comparison reduces timing-leak risk.\n- Protected routes redirect unauthenticated users to `/login`, and the context exposes `login`/`logout` helpers for UI components.\n\n## Data layer and APIs\n- Seed data lives in `src/data/analyses.json` and is loaded on first run.\n- `src/api/entities.js` exports `DecisionAnalysis`, a service exposing CRUD operations against the stored analyses:\n - `list(orderBy)` sorts results (e.g., `-created_date`).\n - `get(id)` retrieves a specific analysis or throws if missing.\n - `create(payload)` inserts a new analysis, generating IDs and timestamps as needed.\n - `update(id, updates)` merges changes and refreshes the `updated_date` stamp.\n - `delete(id)` removes an analysis by ID.\n- Storage writes to `localStorage` when available, with an in-memory fallback for environments without persistent browser storage.\n- After mutations, the service dispatches a custom `analyses_changed` DOM event so dashboard and list views can refresh automatically.\n\n## Styling and UI system\n- Tailwind CSS provides utility-first styling; `clsx` and `tailwind-merge` help compose class names safely.\n- Radix UI primitives and Shadcn-inspired components live under `src/components/ui`, covering dialogs, popovers, dropdowns, and other interactive elements.\n- Icons use `lucide-react`; motion and animation rely on `framer-motion` and `tailwindcss-animate`.\n- Charts and summaries on the dashboard use `recharts` for data visualization.\n\n## Utilities and helpers\n- `src/lib/utils.js` offers the `cn` helper for class name merging.\n- `src/utils` contains routing helpers (`createPageUrl`, `PAGE_ROUTES`) and other shared logic reused across pages and components.\n\n## Testing and quality\n- ESLint configuration lives in `eslint.config.js` and runs via `npm run lint` to enforce consistency across React and hook usage.\n- The project currently relies on manual and exploratory testing alongside linting; no automated test suite is configured in the repository.\n\n- ChatOps interface lets teams approve or override automated fixes.\n- Pluggable policy hooks support vendor-specific add-ons.","overviewTechnicalMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n h2: \"h2\",\n h1: \"h1\",\n p: \"p\",\n ul: \"ul\",\n li: \"li\",\n code: \"code\",\n strong: \"strong\"\n }, _provideComponents(), props.components);\n return _jsxs(_Fragment, {\n children: [_jsx(_components.h2, {\n children: \"Overview\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Project Overview\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"The Analytic Hierarchy Process (AHP) app is a Vite + React single-page application that helps teams create, edit, and review decision analyses directly in the browser. It ships with sample scenarios, includes an admin-only dashboard, and operates entirely on client-side storage so it can run on static hosting without server dependencies.\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Key capabilities\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"Admin authentication gate protecting the dashboard and editing workflows.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Creation and editing of AHP analyses with persisted data in the browser.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Dashboard overview cards, recent analysis lists, and quick links into edit flows.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Responsive UI composed from Tailwind CSS utilities, Radix UI primitives, and Shadcn-inspired components.\"\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Client-only data layer that seeds from JSON and syncs via \", _jsx(_components.c
94ode, {\n children: \"localStorage\"\n }), \" (with an in-memory fallback for non-browser contexts).\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"High-level architecture\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"React SPA with hash-based routing:\"\n }), \" Uses \", _jsx(_components.code, {\n children: \"react-router-dom\"\n }), \" v7 hash router so routes work on static hosts without custom rewrite rules.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Feature organization:\"\n }), \" Routed pages live in \", _jsx(_components.code, {\n children: \"src/pages\"\n }), \", shared UI lives in \", _jsx(_components.code, {\n children: \"src/components\"\n }), \" (with \", _jsx(_components.code, {\n children: \"components/ui\"\n }), \" for primitives), and shared logic resides in \", _jsx(_components.code, {\n children: \"src/hooks\"\n }), \" and \", _jsx(_components.code, {\n children: \"src/utils\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"State and persistence:\"\n }), \" Authentication context tracks login status in \", _jsx(_components.code, {\n children: \"localStorage\"\n }), \", while the AHP data service provides CRUD operations backed by local storage and emits change events to keep views synchronized.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Styling and visuals:\"\n }), \" Tailwind CSS powers layout and spacing, Radix UI primitives supply accessibility, and Recharts renders analytics visualizations.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Included libraries (high level)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"React ecosystem: \", _jsx(_components.code, {\n children: \"react\"\n }), \", \", _jsx(_components.code, {\n children: \"react-dom\"\n }), \", \", _jsx(_components.code, {\n children: \"react-router-dom\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Styling \u0026 UI: Tailwind CSS, Radix UI packages, \", _jsx(_components.code, {\n children: \"tailwind-merge\"\n }), \", \", _jsx(_components.code, {\n children: \"clsx\"\n }), \", \", _jsx(_components.code, {\n children: \"lucide-react\"\n }), \", \", _jsx(_components.code, {\n children: \"framer-motion\"\n }), \", \", _jsx(_components.code, {\n children: \"tailwindcss-animate\"\n }), \", \", _jsx(_components.code, {\n children: \"react-resizable-panels\"\n }), \", \", _jsx(_components.code, {\n children: \"embla-carousel-react\"\n }), \", \", _jsx(_components.code, {\n children: \"react-day-picker\"\n }), \", \", _jsx(_components.code, {\n children: \"cmdk\"\n }), \", \", _jsx(_components.code, {\n children: \"sonner\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Forms \u0026 validation: \", _jsx(_components.code, {\n children: \"react-hook-form\"\n }), \", \", _jsx(_components.code, {\n children: \"zod\"\n }), \", \", _jsx(_components.code, {\n children: \"@hookform/resolvers\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Data visualization: \", _jsx(_components.code, {\n children: \"recharts\"\n })]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Utilities and helpers: \", _jsx(_components.code, {\n children: \"date-fns\"\n }
94), \", \", _jsx(_components.code, {\n children: \"class-variance-authority\"\n }), \", \", _jsx(_components.code, {\n children: \"input-otp\"\n }), \", \", _jsx(_components.code, {\n children: \"vaul\"\n }), \", \", _jsx(_components.code, {\n children: \"next-themes\"\n }), \", \", _jsx(_components.code, {\n children: \"prop-types\"\n })]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Technical Details\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Technical Details\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Application structure\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Entry point:\"\n }), \" \", _jsx(_components.code, {\n children: \"src/main.jsx\"\n }), \" mounts the React tree and global providers (e.g., toaster) before rendering the router defined in \", _jsx(_components.code, {\n children: \"src/pages/index.jsx\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Routing:\"\n }), \" Uses \", _jsx(_components.code, {\n children: \"createHashRouter\"\n }), \" from \", _jsx(_components.code, {\n children: \"react-router-dom\"\n }), \" v7. \", _jsx(_components.code, {\n children: \"AuthProvider\"\n }), \" wraps the router, and \", _jsx(_components.code, {\n children: \"RequireAuth\"\n }), \" protects all routes except \", _jsx(_components.code, {\n children: \"/login\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Layout shell:\"\n }), \" \", _jsx(_components.code, {\n children: \"src/pages/Layout.jsx\"\n }), \" provides the global navigation, header, and content container shared by routed pages.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Pages:\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"Dashboard.jsx\"\n }), \" renders overview cards (\", _jsx(_components.code, {\n children: \"StatsOverview\"\n }), \"), recent analyses (\", _jsx(_components.code, {\n children: \"RecentAnalyses\"\n }), \"), and actions to view/edit/delete analyses.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"CreateAnalysis.jsx\"\n }), \" supplies the form-driven experience for building or editing AHP models, using validation helpers and shared UI primitives.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"Login.jsx\"\n }), \" presents the admin authentication form.\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Authentication flow\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Implemented in \", _jsx(_components.code, {\n children: \"src/hooks/useAuth.jsx\"\n }), \" via a React context and \", _jsx(_components.code, {\n children: \"useAuth\"\n }), \" hook.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Credentials come from \", _jsx(_components.code, {\n children: \"VITE_ADMIN_USERNAME\"\n }), \" and \", _jsx(_components.code, {\n children: \"VITE_ADMIN_PASSWORD\"\n }), \" with safe defaults for local development.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Auth state persists in \", _jsx(_components.c
94ode, {\n children: \"localStorage\"\n }), \" under \", _jsx(_components.code, {\n children: \"ahp:auth:v1\"\n }), \"; a constant-time password comparison reduces timing-leak risk.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Protected routes redirect unauthenticated users to \", _jsx(_components.code, {\n children: \"/login\"\n }), \", and the context exposes \", _jsx(_components.code, {\n children: \"login\"\n }), \"/\", _jsx(_components.code, {\n children: \"logout\"\n }), \" helpers for UI components.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Data layer and APIs\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Seed data lives in \", _jsx(_components.code, {\n children: \"src/data/analyses.json\"\n }), \" and is loaded on first run.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"src/api/entities.js\"\n }), \" exports \", _jsx(_components.code, {\n children: \"DecisionAnalysis\"\n }), \", a service exposing CRUD operations against the stored analyses:\", \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"list(orderBy)\"\n }), \" sorts results (e.g., \", _jsx(_components.code, {\n children: \"-created_date\"\n }), \").\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"get(id)\"\n }), \" retrieves a specific analysis or throws if missing.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"create(payload)\"\n }), \" inserts a new analysis, generating IDs and timestamps as needed.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"update(id, updates)\"\n }), \" merges changes and refreshes the \", _jsx(_components.code, {\n children: \"updated_date\"\n }), \" stamp.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"delete(id)\"\n }), \" removes an analysis by ID.\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Storage writes to \", _jsx(_components.code, {\n children: \"localStorage\"\n }), \" when available, with an in-memory fallback for environments without persistent browser storage.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"After mutations, the service dispatches a custom \", _jsx(_components.code, {\n children: \"analyses_changed\"\n }), \" DOM event so dashboard and list views can refresh automatically.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Styling and UI system\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Tailwind CSS provides utility-first styling; \", _jsx(_components.code, {\n children: \"clsx\"\n }), \" and \", _jsx(_components.code, {\n children: \"tailwind-merge\"\n }), \" help compose class names safely.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Radix UI primitives and Shadcn-inspired components live under \", _jsx(_components.code, {\n children: \"src/components/ui\"\n }), \", covering dialogs, popovers, dropdowns, and other interactive elements.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Icons use \", _jsx(_components.code, {\n children: \"lucide-react\"\n }), \"; motion and animation rely on \", _jsx(_components.code, {\n children: \"framer-motion\"\n }
94), \" and \", _jsx(_components.code, {\n children: \"tailwindcss-animate\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Charts and summaries on the dashboard use \", _jsx(_components.code, {\n children: \"recharts\"\n }), \" for data visualization.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Utilities and helpers\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"src/lib/utils.js\"\n }), \" offers the \", _jsx(_components.code, {\n children: \"cn\"\n }), \" helper for class name merging.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"src/utils\"\n }), \" contains routing helpers (\", _jsx(_components.code, {\n children: \"createPageUrl\"\n }), \", \", _jsx(_components.code, {\n children: \"PAGE_ROUTES\"\n }), \") and other shared logic reused across pages and components.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Testing and quality\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [\"ESLint configuration lives in \", _jsx(_components.code, {\n children: \"eslint.config.js\"\n }), \" and runs via \", _jsx(_components.code, {\n children: \"npm run lint\"\n }), \" to enforce consistency across React and hook usage.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"The project currently relies on manual and exploratory testing alongside linting; no automated test suite is configured in the repository.\"\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"ChatOps interface lets teams approve or override automated fixes.\"\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"Pluggable policy hooks support vendor-specific add-ons.\"\n }), \"\\n\"]\n }), \"\\n\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}}},{"id":4,"title":"Kids Read Fundations","descriptionSlug":"kids-read","tech":["Deployment : Heroku","Next.js 14","React 18","Tailwind CSS","tailwindcss-animate","shadcn/ui","Radix UI","class-variance-authority","tailwind-merge","lucide-react","date-fns","react-hook-form","zod","OpenAI SDK","embla-carousel-react","framer-motion","recharts"],"year":"2025","link":"https://manistechmind.com","role":"Software Architect \u0026 Lead Developer","images":[{"image":"/img/kids-read-1.png","description":"-","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Kids Read Fundations showcase 1"}
94,{"image":"/img/kids-read-2.gif","description":"-","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Kids Read Fundations showcase 2"}],"image":"/img/kids-read-1.png","description":"## Overview\n\n# KidsRead Project Overview\n\n## What the project is\nKidsRead is a Next.js 14 application that acts as a joyful reading companion. It lets families track reading sessions, celebrate progress with photos and audio feedback, and administer lesson content from a single web experience. The app runs entirely on a Node/Next.js runtime (Heroku-friendly) and ships with built-in API routes for speech synthesis and image uploads so no separate backend service is required.\n\n## Architectural layout\n- **Next.js pages router**: The app uses the classic `src/pages` router for rapid rendering and API co-location. Public routes include the dashboard (`/`), reading workflow (`/reading`), history timeline (`/history`), and lesson admin (`/admin`). Shared chrome and navigation live in `src/components/Layout.jsx` and are applied via `_app.jsx`.\n- **Feature views**: Each route delegates to a corresponding view in `src/views/`. The views compose UI primitives, hooks, and data utilities to keep page files thin and focused on routing.\n- **State and data flow**: Reading logs are managed client-side through the `useReadingHistory` hook, which persists to `localStorage`, computes streaks/aggregates with `date-fns`, and exposes CRUD helpers consumed by the dashboard and history views. Lesson content is sourced from static JSON in `src/data/lessons.js` and managed with `useLessonManager`.\n- **UI system**: Visual components are built with Tailwind CSS and a set of shadcn-inspired primitives in `src/components/ui/` (buttons, cards, badges, dialogs, etc.). Icons come from `lucide-react`. Layout and typography lean on Tailwind utility classes configured in `tailwind.config.js` and global styles in `src/index.css`.\n- **Utilities and helpers**: API callers live in `src/api/` to encapsulate HTTP details for speech generation and photo uploads. Shared helpers (formatting, randomization, demo data) reside under `src/utils/` and `src/lib/`.\n\n## Implementation walk-through\n- **App shell**: `_app.jsx` wraps every page with `Layout`, sets up Tailwind styles, and keeps navigation consistent. `Layout.jsx` renders the top navigation, active route highlighting, and a responsive content container.\n- **Dashboard (`/`)**: `Dashboard.jsx` surfaces streak metrics, calendar-driven log selection, recent celebrations, and an inline `LogEditor` modal. It pulls computed stats (`minutesThisWeek`, `currentStreak`, `longestStreak`, etc.) directly from `useReadingHistory`.\n- **Reading flow (`/reading`)**: `Reading.jsx` guides a single reading session with lesson details, timers, and a capture form. It can post photo evidence and speech feedback through the shared API utilities.\n- **History (`/history`)**: `History.jsx` renders the chronological feed of log entries with filters and summary badges, again powered by `useReadingHistory` state.\n- **Admin (`/admin`)**: `Admin.jsx` exposes lesson editing, visibility toggles, and ordering controls using `useLessonManager`, which manages the lesson list in memory for quick adjustments.\n- **Styling**: `src/index.css` and `App.css` provide Tailwind base layers plus custom gradients, card shadows, and responsive tweaks. `tailwind.config.js` registers design tokens, font sizes, and animation keyframes for the UI kit.\n\n## API surface (Next.js API routes)\n- `POST /api/speech` (`src/pages/api/speech.js`)\n - **Purpose**: Streams OpenAI Text-to-Speech (gpt-4o-mini-tts) audio for any submitted text.\n - **Input**: JSON `{ text: string, voice?: string, format?: 'mp3' | 'opus' }`.\n - **Output**: Binary audio (`audio/mpeg` or `audio/ogg`). Returns errors with helpful hints if the endpoint is unreachable or misconfigured.\n - **Dependencies**: Uses the `open
94ai` SDK and Node streams; requires `OPENAI_API_KEY`.\n- `POST /api/upload-photo` (`src/pages/api/upload-photo.js`)\n - **Purpose**: Accepts base64 image uploads and saves them to `public/uploads` on the Next.js server.\n - **Input**: JSON `{ fileName?: string, dataUrl: 'data:\u003cmime\u003e;base64,\u003cpayload\u003e' }`.\n - **Output**: JSON `{ url: string }` pointing to the saved file for immediate rendering.\n - **Notes**: Uses Node `fs/promises` and `path` to ensure the upload directory exists and to sanitize filenames.\n\n## Client-side API helpers\n- `fetchSpeechAudio` in `src/api/speech.js` posts to `/api/speech` (or `NEXT_PUBLIC_SPEECH_API_ENDPOINT` override) and returns an object URL + MIME type for playback, with detailed error hints for routing/hosting issues.\n- `uploadPhoto` in `src/api/uploadPhoto.js` posts base64 payloads to `/api/upload-photo` and returns the served URL so views can immediately render the new image.\n\n## Key libraries and why they are used\n- **Next.js \u0026amp; React 18**: Core app framework and rendering.\n- **Tailwind CSS + tailwindcss-animate**: Utility-first styling and lightweight animations.\n- **shadcn/ui primitives (Radix UI + class-variance-authority + tailwind-merge)**: Accessible composable UI building blocks.\n- **lucide-react**: Iconography throughout the dashboard and reading flows.\n- **date-fns**: Date math for streaks, calendar highlights, and session summaries.\n- **react-hook-form \u0026amp; zod**: Form handling and validation within editors and admin tools.\n- **OpenAI SDK**: Server-side speech generation in the `/api/speech` route.\n- **embla-carousel-react, framer-motion, recharts**: Interactive UI affordances (carousels, animations, charts) used across the views.\n\n## Deployment notes\n- The app builds with `npm run build` and runs with `npm run start` on the default Next.js port. Heroku deployment only needs a Node runtime and the `OPENAI_API_KEY` environment variable. No Netlify-specific files remain, and API routes are hosted by the Next.js server alongside the frontend.\n\n## Technical Details\n\n# KidsRead - Next.js\n\nKidsRead is now a Next.js application with built-in API routes for speech synthesis and photo uploads. The app ships with a dashboard, reading workflow, history tracking, and an admin view for lessons.\n\n## Running locally\n\n```bash\nnpm install\nnpm run dev\n```\n\nThe app runs on the default Next.js port (3000). API routes live under `/api/*` and run in the same server process.\n\n### Environment variables\n\n| Variable | Purpose |\n| --- | --- |\n| `OPENAI_API_KEY` | Required for the `/api/speech` route that streams speech audio from OpenAI. |\n| `NEXT_PUBLIC_SPEECH_API_ENDPOINT` | Optional override for the speech endpoint. Defaults to `/api/speech`. |\n\n### Speech service\n\nThe `/api/speech` route calls OpenAI's `gpt-4o-mini-tts` model and returns binary audio. If you point `NEXT_PUBLIC_SPEECH_API_ENDPOINT` to a hosted endpoint, make sure that route accepts the same payload `{ text, voice, format }` and returns audio bytes.\n\n### Photo uploads\n\nPhoto uploads now post to `/api/upload-photo`. The route accepts a `{ fileName, dataUrl }` payload, writes the decoded bytes to `public/uploads/`, and responds with a URL the UI can render. On ephemeral hosts you may want to swap this storage for object storage (S3, etc.); the handler is a single file in `src/pages/api/upload-photo.js`.\n\n## Building for production\n\n```bash\nnpm run build\nnpm run start\n```\n\nDeploy the built Next.js app to Heroku or any Node-friendly host. No Netlify-specific configuration or functions are required.\n\nFor more information and support, please contact Base44 support at [email protected].","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n h2: \"h2\",\n h1: \"h1\",\n p: \"p\",\n ul: \"ul\",\n li: \"li\",\n strong: \"strong\",\n code: \"code\",\n pre: \"pre\",\n h3
94: \"h3\",\n table: \"table\",\n thead: \"thead\",\n tr: \"tr\",\n th: \"th\",\n tbody: \"tbody\",\n td: \"td\",\n a: \"a\"\n }, _provideComponents(), props.components);\n return _jsxs(_Fragment, {\n children: [_jsx(_components.h2, {\n children: \"Overview\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"KidsRead Project Overview\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"What the project is\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"KidsRead is a Next.js 14 application that acts as a joyful reading companion. It lets families track reading sessions, celebrate progress with photos and audio feedback, and administer lesson content from a single web experience. The app runs entirely on a Node/Next.js runtime (Heroku-friendly) and ships with built-in API routes for speech synthesis and image uploads so no separate backend service is required.\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Architectural layout\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Next.js pages router\"\n }), \": The app uses the classic \", _jsx(_components.code, {\n children: \"src/pages\"\n }), \" router for rapid rendering and API co-location. Public routes include the dashboard (\", _jsx(_components.code, {\n children: \"/\"\n }), \"), reading workflow (\", _jsx(_components.code, {\n children: \"/reading\"\n }), \"), history timeline (\", _jsx(_components.code, {\n children: \"/history\"\n }), \"), and lesson admin (\", _jsx(_components.code, {\n children: \"/admin\"\n }), \"). Shared chrome and navigation live in \", _jsx(_components.code, {\n children: \"src/components/Layout.jsx\"\n }), \" and are applied via \", _jsx(_components.code, {\n children: \"_app.jsx\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Feature views\"\n }), \": Each route delegates to a corresponding view in \", _jsx(_components.code, {\n children: \"src/views/\"\n }), \". The views compose UI primitives, hooks, and data utilities to keep page files thin and focused on routing.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"State and data flow\"\n }), \": Reading logs are managed client-side through the \", _jsx(_components.code, {\n children: \"useReadingHistory\"\n }), \" hook, which persists to \", _jsx(_components.code, {\n children: \"localStorage\"\n }), \", computes streaks/aggregates with \", _jsx(_components.code, {\n children: \"date-fns\"\n }), \", and exposes CRUD helpers consumed by the dashboard and history views. Lesson content is sourced from static JSON in \", _jsx(_components.code, {\n children: \"src/data/lessons.js\"\n }), \" and managed with \", _jsx(_components.code, {\n children: \"useLessonManager\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"UI system\"\n }), \": Visual components are built with Tailwind CSS and a set of shadcn-inspired primitives in \", _jsx(_components.code, {\n children: \"src/components/ui/\"\n }), \" (buttons, cards, badges, dialogs, etc.). Icons come from \", _jsx(_components.code, {\n children: \"lucide-react\"\n }), \". Layout and typography lean on Tailwind utility classes configured in \", _jsx(_components.code, {\n children: \"tailwind.config.js\"\n }), \" and global styles in \", _jsx(_components.code, {\n children: \"src/index.css\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Utilities and helpers\"\n }
94), \": API callers live in \", _jsx(_components.code, {\n children: \"src/api/\"\n }), \" to encapsulate HTTP details for speech generation and photo uploads. Shared helpers (formatting, randomization, demo data) reside under \", _jsx(_components.code, {\n children: \"src/utils/\"\n }), \" and \", _jsx(_components.code, {\n children: \"src/lib/\"\n }), \".\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Implementation walk-through\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"App shell\"\n }), \": \", _jsx(_components.code, {\n children: \"_app.jsx\"\n }), \" wraps every page with \", _jsx(_components.code, {\n children: \"Layout\"\n }), \", sets up Tailwind styles, and keeps navigation consistent. \", _jsx(_components.code, {\n children: \"Layout.jsx\"\n }), \" renders the top navigation, active route highlighting, and a responsive content container.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsxs(_components.strong, {\n children: [\"Dashboard (\", _jsx(_components.code, {\n children: \"/\"\n }), \")\"]\n }), \": \", _jsx(_components.code, {\n children: \"Dashboard.jsx\"\n }), \" surfaces streak metrics, calendar-driven log selection, recent celebrations, and an inline \", _jsx(_components.code, {\n children: \"LogEditor\"\n }), \" modal. It pulls computed stats (\", _jsx(_components.code, {\n children: \"minutesThisWeek\"\n }), \", \", _jsx(_components.code, {\n children: \"currentStreak\"\n }), \", \", _jsx(_components.code, {\n children: \"longestStreak\"\n }), \", etc.) directly from \", _jsx(_components.code, {\n children: \"useReadingHistory\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsxs(_components.strong, {\n children: [\"Reading flow (\", _jsx(_components.code, {\n children: \"/reading\"\n }), \")\"]\n }), \": \", _jsx(_components.code, {\n children: \"Reading.jsx\"\n }), \" guides a single reading session with lesson details, timers, and a capture form. It can post photo evidence and speech feedback through the shared API utilities.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsxs(_components.strong, {\n children: [\"History (\", _jsx(_components.code, {\n children: \"/history\"\n }), \")\"]\n }), \": \", _jsx(_components.code, {\n children: \"History.jsx\"\n }), \" renders the chronological feed of log entries with filters and summary badges, again powered by \", _jsx(_components.code, {\n children: \"useReadingHistory\"\n }), \" state.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsxs(_components.strong, {\n children: [\"Admin (\", _jsx(_components.code, {\n children: \"/admin\"\n }), \")\"]\n }), \": \", _jsx(_components.code, {\n children: \"Admin.jsx\"\n }), \" exposes lesson editing, visibility toggles, and ordering controls using \", _jsx(_components.code, {\n children: \"useLessonManager\"\n }), \", which manages the lesson list in memory for quick adjustments.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Styling\"\n }), \": \", _jsx(_components.code, {\n children: \"src/index.css\"\n }), \" and \", _jsx(_components.code, {\n children: \"App.css\"\n }), \" provide Tailwind base layers plus custom gradients, card shadows, and responsive tweaks. \", _jsx(_components.code, {\n children: \"tailwind.config.js\"\n }), \" registers design tokens, font sizes, and animation keyframes for the UI kit.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"API surface (Next.js API routes)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.c
94ode, {\n children: \"POST /api/speech\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/pages/api/speech.js\"\n }), \")\", \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Purpose\"\n }), \": Streams OpenAI Text-to-Speech (gpt-4o-mini-tts) audio for any submitted text.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Input\"\n }), \": JSON \", _jsx(_components.code, {\n children: \"{ text: string, voice?: string, format?: 'mp3' | 'opus' }\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Output\"\n }), \": Binary audio (\", _jsx(_components.code, {\n children: \"audio/mpeg\"\n }), \" or \", _jsx(_components.code, {\n children: \"audio/ogg\"\n }), \"). Returns errors with helpful hints if the endpoint is unreachable or misconfigured.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Dependencies\"\n }), \": Uses the \", _jsx(_components.code, {\n children: \"openai\"\n }), \" SDK and Node streams; requires \", _jsx(_components.code, {\n children: \"OPENAI_API_KEY\"\n }), \".\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"POST /api/upload-photo\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/pages/api/upload-photo.js\"\n }), \")\", \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Purpose\"\n }), \": Accepts base64 image uploads and saves them to \", _jsx(_components.code, {\n children: \"public/uploads\"\n }), \" on the Next.js server.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Input\"\n }), \": JSON \", _jsx(_components.code, {\n children: \"{ fileName?: string, dataUrl: 'data:\u003cmime\u003e;base64,\u003cpayload\u003e' }\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Output\"\n }), \": JSON \", _jsx(_components.code, {\n children: \"{ url: string }\"\n }), \" pointing to the saved file for immediate rendering.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Notes\"\n }), \": Uses Node \", _jsx(_components.code, {\n children: \"fs/promises\"\n }), \" and \", _jsx(_components.code, {\n children: \"path\"\n }), \" to ensure the upload directory exists and to sanitize filenames.\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Client-side API helpers\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"fetchSpeechAudio\"\n }), \" in \", _jsx(_components.code, {\n children: \"src/api/speech.js\"\n }), \" posts to \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \" (or \", _jsx(_components.code, {\n children: \"NEXT_PUBLIC_SPEECH_API_ENDPOINT\"\n }), \" override) and returns an object URL + MIME type for playback, with detailed error hints for routing/hosting issues.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.c
94ode, {\n children: \"uploadPhoto\"\n }), \" in \", _jsx(_components.code, {\n children: \"src/api/uploadPhoto.js\"\n }), \" posts base64 payloads to \", _jsx(_components.code, {\n children: \"/api/upload-photo\"\n }), \" and returns the served URL so views can immediately render the new image.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Key libraries and why they are used\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Next.js \u0026 React 18\"\n }), \": Core app framework and rendering.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Tailwind CSS + tailwindcss-animate\"\n }), \": Utility-first styling and lightweight animations.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"shadcn/ui primitives (Radix UI + class-variance-authority + tailwind-merge)\"\n }), \": Accessible composable UI building blocks.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"lucide-react\"\n }), \": Iconography throughout the dashboard and reading flows.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"date-fns\"\n }), \": Date math for streaks, calendar highlights, and session summaries.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"react-hook-form \u0026 zod\"\n }), \": Form handling and validation within editors and admin tools.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"OpenAI SDK\"\n }), \": Server-side speech generation in the \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \" route.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"embla-carousel-react, framer-motion, recharts\"\n }), \": Interactive UI affordances (carousels, animations, charts) used across the views.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Deployment notes\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"The app builds with \", _jsx(_components.code, {\n children: \"npm run build\"\n }), \" and runs with \", _jsx(_components.code, {\n children: \"npm run start\"\n }), \" on the default Next.js port. Heroku deployment only needs a Node runtime and the \", _jsx(_components.code, {\n children: \"OPENAI_API_KEY\"\n }), \" environment variable. No Netlify-specific files remain, and API routes are hosted by the Next.js server alongside the frontend.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Technical Details\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"KidsRead - Next.js\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"KidsRead is now a Next.js application with built-in API routes for speech synthesis and photo uploads. The app ships with a dashboard, reading workflow, history tracking, and an admin view for lessons.\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Running locally\"\n }), \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm install\\nnpm run dev\\n\"\n })\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"The app runs on the default Next.js port (3000). API routes live under \", _jsx(_components.code, {\n children: \"/api/*\"\n }), \" and run in the same server process.\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Environment variables\"\n }), \"\\n\", _jsxs(_components.table, {\n children: [_jsx(_components.thead, {\n children: _jsxs(_components.tr, {\n children: [_jsx(_components.th, {\n children: \"Variable\"\n }), _jsx(_components.th, {\n children: \"Purpose\"\n })]\n })\n }), _jsxs(_components.tbody, {\n children: [_jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: _jsx(_components.c
94ode, {\n children: \"OPENAI_API_KEY\"\n })\n }), _jsxs(_components.td, {\n children: [\"Required for the \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \" route that streams speech audio from OpenAI.\"]\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: _jsx(_components.code, {\n children: \"NEXT_PUBLIC_SPEECH_API_ENDPOINT\"\n })\n }), _jsxs(_components.td, {\n children: [\"Optional override for the speech endpoint. Defaults to \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \".\"]\n })]\n })]\n })]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Speech service\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"The \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \" route calls OpenAI's \", _jsx(_components.code, {\n children: \"gpt-4o-mini-tts\"\n }), \" model and returns binary audio. If you point \", _jsx(_components.code, {\n children: \"NEXT_PUBLIC_SPEECH_API_ENDPOINT\"\n }), \" to a hosted endpoint, make sure that route accepts the same payload \", _jsx(_components.code, {\n children: \"{ text, voice, format }\"\n }), \" and returns audio bytes.\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Photo uploads\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"Photo uploads now post to \", _jsx(_components.code, {\n children: \"/api/upload-photo\"\n }), \". The route accepts a \", _jsx(_components.code, {\n children: \"{ fileName, dataUrl }\"\n }), \" payload, writes the decoded bytes to \", _jsx(_components.code, {\n children: \"public/uploads/\"\n }), \", and responds with a URL the UI can render. On ephemeral hosts you may want to swap this storage for object storage (S3, etc.); the handler is a single file in \", _jsx(_components.code, {\n children: \"src/pages/api/upload-photo.js\"\n }), \".\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Building for production\"\n }), \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm run build\\nnpm run start\\n\"\n })\n }), \"\\n\", _jsx(_components.p, {\n children: \"Deploy the built Next.js app to Heroku or any Node-friendly host. No Netlify-specific configuration or functions are required.\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"For more information and support, please contact Base44 support at \", _jsx(_components.a, {\n href: \"mailto:[email protected]\",\n children: \"[email protected]\"\n }), \".\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"requirements":"# Kids Read App Requirements \u0026amp; Lesson Plan\n\n## 1. Overview\n\n### 1.1 Purpose\nThe Kids Read app delivers a structured literacy program inspired by UFLI Foundations. It guides emergent readers (PreKâGrade 2) through explicit, cumulative instruction in phonemic awareness, phonics, decoding, encoding, high-frequency words, and connected reading.\n\n### 1.2 Goals\n- Equip children ages 4â7 with foundational reading skills through a daily 15â25 minute routine.\n- Provide parents and caregivers with actionable insights, progress tracking, and at-home practice tools.\n- Offer teachers a supplemental platform that mirrors evidence-based classroom instruction.\n\n### 1.3 Success Metrics\n- Lesson completion rate (target â¥80% of assigned lessons per week).\n- Mastery progression (â¥90% accuracy on cumulative review checks).\n- Parent engagement (weekly dashboard views, messages sent, practice sessions logged).\n- Retention and motivation (badge collection rate, streak maintenance).\n\n## 2. Target Users\n\n| Persona | Needs | Key Features |\n| --- | --- | --- |\n| Early Reader (Age 4â7) | Engaging, scaffolded practice; clear instructions; positive feedback | Mascot guidance, touch-friendly games, voice narration |\n| Parent/Caregiver | Visibility into progress; simple support cues; offline flexibility | Weekly summaries, practice suggestions, downloadable lessons |\n| Teacher (optional) | Alignment to UFLI scope; monitoring tools; homework coordination | Class management, review assignments, data export |\n\n## 3. Functional Requirements\n\n### 3.1 Lesson Experience\n1. **Daily Lesson Flow:** Each lesson contains five components in order: Warm-Up, Phonics Instru
94ction, Word Work, High-Frequency Word Practice, Connected Reading.\n2. **Timing:** Total lesson duration configurable between 15â25 minutes with pacing indicators.\n3. **Narration \u0026amp; Animations:** All child-facing instructions voiced and optionally captioned; mascot animations provide feedback.\n4. **Interactive Activities:** Tap/drag interactions for phoneme manipulation, blending boards, spelling tiles, and story-based comprehension checks.\n5. **Gamified Rewards:** Stars for component completion, badges for streaks (5-day, 10-day, review mastery), collectible items tied to story themes.\n\n### 3.2 Weekly Flow \u0026amp; Scheduling\n1. **Structured Week:** Days 1â3 introduce new graphemes and words; Day 4 focuses on fluency, expression, and rereading; Day 5 dedicated to cumulative review plus parent check-in.\n2. **Calendar \u0026amp; Notifications:** Push/email reminders for upcoming lessons, missed days, and review prompts.\n3. **Adaptive Review:** When a learner scores below 80% on end-of-day checks, the system schedules an extra practice session.\n\n### 3.3 Parent Dashboard\n1. **Progress Overview:** Displays phonemes mastered, sight words learned, decodable stories completed, and streak status.\n2. **Weekly Summary Report:** Highlights strengths, target practice areas, and recommended offline games; delivered via in-app card and optional email.\n3. **Lesson Playback:** Allows parents to preview upcoming lessons and replay completed activities.\n4. **Multiple Learners:** Support for managing multiple child profiles with separate progress tracking.\n\n### 3.4 Content Management\n1. **Scope \u0026amp; Sequence Engine:** Stores lesson metadata (phonics focus, high-frequency word, decodable vocabulary, story, comprehension questions).\n2. **Asset Library:** Central repository for audio clips, story art, manipulatives, and printable resources tagged by lesson.\n3. **Localization-Ready:** Content architecture supports translation of UI text, narration scripts, and decodable stories.\n\n### 3.5 Gamification \u0026amp; Engagement\n1. **Mascot Companion:** Character customization unlocked through badge milestones;
94 mascot delivers praise, hints, and transition cues.\n2. **Mini-Games:** Optional replayable activities that reinforce recent phonics skills (e.g., sound sorting, word building).\n3. **Achievements:** Tiered badges for consistency, accuracy, and fluency with shareable certificates.\n\n### 3.6 Accessibility \u0026amp; Support\n1. **Audio \u0026amp; Visual Support:** Adjustable narration speed, text highlighting in sync with audio, dyslexia-friendly font toggle.\n2. **Input Options:** Touch, mouse, and keyboard navigation; large hit-targets for younger learners.\n3. **Help \u0026amp; Guidance:** Parent tips embedded in each lesson; contextual help icon linking to how-to videos.\n\n## 4. Non-Functional Requirements\n- **Performance:** Lesson screens load in under 2 seconds on mid-range tablets; assets pre-fetched to minimize delays.\n- **Reliability:** Autosave progress checkpoints; resume from last completed component if connection drops.\n- **Security \u0026amp; Privacy:** COPPA and GDPR-K compliant; parental consent workflow; secure storage of child data.\n- **Offline Availability:** Ability to download up to one week of lessons; progress syncs when connection restores.\n- **Cross-Platform:** Responsive design for tablets and phones; native wrappers for iOS and Android.\n\n## 5. Content Architecture\n\n### 5.1 Lesson Metadata Schema (conceptual)\n```json\n{\n \"lessonId\": \"L01\",\n \"title\": \"Sam Sat\",\n \"phonicsFocus\": [\"/a/\", \"m\", \"s\", \"t\"],\n \"highFrequencyWord\": \"the\",\n \"decodableWords\": [\"mat\", \"sat\", \"Sam\"],\n \"story\": {\n \"title\": \"Sam Sat\",\n \"sentences\": [\"Sam sat on the mat.\"],\n \"comprehensionQuestions\": [\n {\n \"prompt\": \"Who sat on the mat?\",\n \"answerChoices\": [\"Sam\", \"Dad\", \"Cat\"],\n \"correctAnswer\": \"Sam\"\n }\n ]\n },\n \"activities\": {\n \"warmUp\": \"Say map. Change /m/ to /s/ â sap.\",\n \"phonics\": \"Introduce letter-sound correspondences for m, s, t, a.\",\n \"wordWork\": [\"mat\", \"sat\", \"Sam\"],\n \"highFrequency\": \"the\",\n \"connectedReading\": \"Sam sat on the mat.\"\n }\n}\n```\n\n### 5.2 Asset Types\n- **Audio:** Narration, phoneme articulation, sound effects.\n- **Visual:** Character art, story illustrations, manipulatives, badge icons.\n- **Data:** JSON lesson definitions, progress states, badge inventories.\n\n## 6. Lesson Roadmap (First 10 Lessons)\n\n| Lesson | Title | Phonics Focus | High-Frequency Word | Decodable Words | Story Summary |\n| --- | --- | --- | --- | --- | --- |\n| 1 | Sam Sat | /a/ short a; m, s, t | the | mat, sat, Sam | Sam sits on a mat. |\n| 2 | A Man and a Pan | +n, p | a | man, pan, nap | A man uses a pan. |\n| 3 | Dad is Sad | +d | I | dad, sad, mad | Dad and the narrator feel sad. |\n| 4 | The Rag | +r, g | to | rag, ran, tag | Sam runs to get a rag. |\n| 5 | A Big Bag | Review short a set | go | fan, bag, cat | A cat hides in a bag. |\n| 6 | It is a Lid | Short i introduction; i, l | is | sit, lip, lid | Naming an object as a lid. |\n| 7 | Him and His Hat | +h, b | in | hid, bit, him | A boy hides his hat in a bin. |\n| 8 | A Kid on a Mat | +f, k | on | fin, kid, kit | A kid and a mat adventure. |\n| 9 | Cat on the Cot | Short o introduction; c, o | at | cot, cop, cat | A cat rests on a cot. |\n| 10 | We Can Sit | Review short a/i/o blends | we | mix, can, top | Friends sit together. |\n\n## 7. Detailed Lesson Designs\nEach lesson follows the same five-part structure with tailored activities, vocabulary, and comprehension checks.\n\n### Lesson 1 â Sam Sat (L01)\n- **Warm-Up (Phonemic Awareness):** Say *map*. Change /m/ to /s/ to make *sap*.\n- **Phonics Instruction:** Introduce letter-sound correspondences for *m, s, t, a* using sound cards, articulation videos, and tracing animations.\n- **Word Work:** Blend and segment *mat, sat, Sam* using digital letter tiles and âtap and swipeâ blending board.\n- **High-Frequency Word Practice:** Teach *the* with skywriting, rainbow tracing, and sentence building.\n- **Connected Reading:** Decodable story âSam Satâ with audio support, highlight-on-read, and comprehension question: âWho sat on the mat?â\n\n### Lesson 2 â A Man and a Pan (L02)\n- **Warm-Up:** Clap when two words rhyme (e.g., *man, pan* vs. *dog*).\n- **Phonics Instru
94ction:** Add letters *n* and *p*; practice articulating /n/ and /p/; air-write the letters.\n- **Word Work:** Build *man, pan, nap*; dictation activity typing or selecting letter tiles to spell words.\n- **High-Frequency Word:** Introduce *a*; use phrase practice (âa man,â âa panâ).\n- **Connected Reading:** Story âA Man and a Pan.â Comprehension question: âWhat does the man have?â\n\n### Lesson 3 â Dad is Sad (L03)\n- **Warm-Up:** Say *mad*. Change /m/ to /d/ to make *dad*.\n- **Phonics Instruction:** Introduce letter *d* with mouth formation video and handwriting practice.\n- **Word Work:** Blend *sad, mad, dad*; rapid-fire decoding game where learners tap the spoken word.\n- **High-Frequency Word:** Teach *I* with sentence stems (âI am ___â).\n- **Connected Reading:** Story âDad is Sad.â Comprehension: âWho is sad?â\n\n### Lesson 4 â The Rag (L04)\n- **Warm-Up:** Identify the word without the short a sound among *rag, ran, dog*.\n- **Phonics Instruction:** Add *r* and *g*; students trace letter paths while hearing articulation cues.\n- **Word Work:** Segment and blend *rag, ran, tag*; word sort by initial consonant.\n- **High-Frequency Word:** Teach *to*; build phrases (âto the ragâ).\n- **Connected Reading:** Story âThe Rag.â Comprehension: âWhere did Sam run?â\n\n### Lesson 5 â A Big Bag (L05)\n- **Warm-Up:** Clap when you hear /a/ in *bag, fan, cat* vs. *pig*.\n- **Phonics Instruction:** Review all short a sounds introduced in Lessons 1â4 with cumulative blending board.\n- **Word Work:** Decode and encode *fan, bag, cat*; timed fluency strips for short a words.\n- **High-Frequency Word:** Review *the, a, I, to* and introduce *go*.\n- **Connected Reading:** Story âA Big Bag.â Comprehension: âWhat is in the bag?â\n\n### Lesson 6 â It is a Lid (L06)\n- **Warm-Up:** Say *lip*. Change /l/ to /s/ to make *sip*.\n- **Phonics Instruction:** Introduce short i with letters *i* and *l*; mouth position video and tactile tracing.\n- **Word Work:** Blend *sit, lip, lid*; push sound buttons to segment words.\n- **High-Frequency Word:** Teach *is*; sentence practice (âIt is ___â).\n- **Connected Reading:** Story âIt is a Lid.â Comprehension: âWhat is it?â\n\n### Lesson 7 â Him and His Hat (L07)\n- **Warm-Up:** Identify rhymes (*him, dim, rim*).\n- **Phonics Instruction:** Add *h* and *b*; practice breathy /h/ and voiced /b/ sounds.\n- **Word Work:** Decode *hid, bit, him*; digital magnetic board to build words.\n- **High-Frequency Word:** Teach *in*; phrase practice (âin a binâ).\n- **Connected Reading:** Story âHim and His Hat.â Comprehension: âWhere did he hide the hat?â\n\n### Lesson 8 â A Kid on a Mat (L08)\n- **Warm-Up:** Say *kit*. Change /t/ to /d/ to make *kid*.\n- **Phonics Instruction:** Add *f* and *k*; letter formation tracing with guided strokes.\n- **Word Work:** Blend *fin, kid, kit*; real vs. nonsense word sort using introduced graphemes.\n- **High-Frequency Word:** Teach *on* with prepositional phrase practice.\n- **Connected Reading:** Story âA Kid on a Mat.â Comprehension: âWho is on the mat?â\n\n### Lesson 9 â Cat on the Cot (L09)\n- **Warm-Up:** Which word has the /o/ sound? (*cot, cat, sit*).\n- **Phonics Instruction:** Introduce short o with letters *c* and *o*; letter tracing with sand animation.\n- **Word Work:** Decode *cot, cop, cat*; switch-it activity replacing initial consonants.\n- **High-Frequency Word:** Review prior words and introduce *at*.\n- **Connected Reading:** Story âCat on the Cot.â Comprehension: âWhere is the cat?â\n\n### Lesson 10 â We Can Sit (L10)\n- **Warm-Up:** Say *mix*. Change /m/ to /f/ to make *fix*.\n- **Phonics Instruction:** Spiral review of short a, i, o with mixed consonants; emphasis on automaticity.\n- **Word Work:** Decode *mix, can, top*; timed blending game measuring words per minute.\n- **High-Frequency Word:** Introduce *we*; collaborative sentence building.\n- **Connected Reading:** Story âWe Can Sit.â Comprehension: âWhat can we do?â\n\n## 8. Assessment \u0026amp; Analytics\n- **In-Lesson Checks:** Quick tap/click responses recorded for each activity; accuracy per phoneme and word tracked.\n- **End-of-Lesson Review:** 3-question quiz mixing decoding, encoding, and comprehension; determines mastery level.\n- **Progression Logic:** Unlock next lesson after completing current lesson with â¥70% accuracy; below threshold triggers reteach module.\n- **Reporting:** Parents receive weekly email digest; teachers can export CSV with student performance per lesson component.\n\n## 9. Technical Considerations\n- **Frontend:** React
94+ Vite + Tailwind (existing stack). Component modules for activities, narration controls, progress indicators.\n- **State Management:** Centralized lesson state using Zustand or Redux Toolkit; persistence via local storage with cloud sync.\n- **Backend (future):** Node/Express or serverless functions managing user accounts, lesson data, analytics, and notifications.\n- **Content Delivery:** CDN hosting for audio/visual assets; caching strategies for offline playback.\n\n## 10. Future Enhancements\n- **Voice Recognition:** Evaluate child oral reading; provide pronunciation feedback.\n- **Adaptive Sequencing:** AI-driven recommendations based on error patterns.\n- **Parent Customization:** Ability to add personal word lists or home names.\n- **Classroom Mode:** Teacher dashboards with group assignments, seating charts, and printable materials.\n\n---\n\nThis document provides a combined roadmap for the applicationâs functionality and the curriculum for the first ten lessons, ensuring alignment between instructional design and product development.","requirementsMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n h1: \"h1\",\n h2: \"h2\",\n h3: \"h3\",\n p: \"p\",\n ul: \"ul\",\n li: \"li\",\n table: \"table\",\n thead: \"thead\",\n tr: \"tr\",\n th: \"th\",\n tbody: \"tbody\",\n td: \"td\",\n ol: \"ol\",\n strong: \"strong\",\n pre: \"pre\",\n code: \"code\",\n em: \"em\",\n hr: \"hr\"\n }, _provideComponents(), props.components);\n return _jsxs(_Fragment, {\n children: [_jsx(_components.h1, {\n children: \"Kids Read App Requirements \u0026 Lesson Plan\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"1. Overview\"\n }), \"\\n\", _jsx(_components.h3, {\n children: \"1.1 Purpose\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"The Kids Read app delivers a structured literacy program inspired by UFLI Foundations. It guides emergent readers (PreKâGrade 2) through explicit, cumulative instruction in phonemic awareness, phonics, decoding, encoding, high-frequency words, and connected reading.\"\n }), \"\\n\", _jsx(_components.h3, {\n children: \"1.2 Goals\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"Equip children ages 4â7 with foundational reading skills through a daily 15â25 minute routine.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Provide parents and caregivers with actionable insights, progress tracking, and at-home practice tools.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Offer teachers a supplemental platform that mirrors evidence-based classroom instruction.\"\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"1.3 Success Metrics\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"Lesson completion rate (target â¥80% of assigned lessons per week).\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Mastery progression (â¥90% accuracy on cumulative review checks).\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Parent engagement (weekly dashboard views, messages sent, practice sessions logged).\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Retention and motivation (badge collection rate, streak maintenance).\"\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"2. Target Users\"\n }), \"\\n\", _jsxs(_components.table, {\n children: [_jsx(_components.thead, {\n children: _jsxs(_components.tr, {\n children: [_jsx(_components.th, {\n children: \"Persona\"\n }), _jsx(_components.th, {\n children: \"Needs\"\n }), _jsx(_components.th, {\n children: \"Key Features\"\n })]\n })\n }), _jsxs(_components.tbody, {\n children: [_jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"Early Reader (Age 4â7)\"\n }), _jsx(_components.td, {\n children: \"Engaging, scaffolded practice; clear instructions; positive feedback\"\n }), _jsx(_components.td, {\n children: \"Mascot guidance, touch-friendly games, voice narration\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"Parent/Caregiver\"\n }), _jsx(_components.td, {\n children: \"Visibility into progress; simple support cues; offline flexibility\"\n }), _jsx(_components.td, {\n children: \"Weekly summaries, practice suggestions, downloadable lessons\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"Teacher (optional)\"\n }), _jsx(_components.td, {\n children: \"Alignment to UFLI scope; monitoring tools; homework coordination\"\n }), _jsx(_components.td, {\n children: \"Class management, review assignments, data export\"\n })]\n })]\n })]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"3. Functional Requirements\"\n }), \"\\n\", _jsx(_components.h3, {\n children: \"3.1 Lesson Experience\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Daily Lesson Flow:\"\n }), \" Each lesson contains five components in order: Warm-Up, Phonics Instru
94ction, Word Work, High-Frequency Word Practice, Connected Reading.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Timing:\"\n }), \" Total lesson duration configurable between 15â25 minutes with pacing indicators.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Narration \u0026 Animations:\"\n }), \" All child-facing instructions voiced and optionally captioned; mascot animations provide feedback.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Interactive Activities:\"\n }), \" Tap/drag interactions for phoneme manipulation, blending boards, spelling tiles, and story-based comprehension checks.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Gamified Rewards:\"\n }), \" Stars for component completion, badges for streaks (5-day, 10-day, review mastery), collectible items tied to story themes.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"3.2 Weekly Flow \u0026 Scheduling\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Structured Week:\"\n }), \" Days 1â3 introduce new graphemes and words; Day 4 focuses on fluency, expression, and rereading; Day 5 dedicated to cumulative review plus parent check-in.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Calendar \u0026 Notifications:\"\n }), \" Push/email reminders for upcoming lessons, missed days, and review prompts.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Adaptive Review:\"\n }), \" When a learner scores below 80% on end-of-day checks, the system schedules an extra practice session.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"3.3 Parent Dashboard\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Progress Overview:\"\n }), \" Displays phonemes mastered, sight words learned, decodable stories completed, and streak status.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Weekly Summary Report:\"\n }), \" Highlights strengths, target practice areas, and recommended offline games; delivered via in-app card and optional email.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Lesson Playback:\"\n }), \" Allows parents to preview upcoming lessons and replay completed activities.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Multiple Learners:\"\n }), \" Support for managing multiple child profiles with separate progress tracking.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"3.4 Content Management\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Scope \u0026 Sequence Engine:\"\n }), \" Stores lesson metadata (phonics focus, high-frequency word, decodable vocabulary, story, comprehension questions).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Asset Library:\"\n }), \" Central repository for audio clips, story art, manipulatives, and printable resources tagged by lesson.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Localization-Ready:\"\n }), \" Content architecture supports translation of UI text, narration scripts, and decodable stories.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"3.5 Gamification \u0026 Engagement\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Mascot Companion:\"\n }), \" Character customization unlocked through badge milestones; mascot delivers praise, hints, and transition cues.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Mini-Games:\"\n }
94), \" Optional replayable activities that reinforce recent phonics skills (e.g., sound sorting, word building).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Achievements:\"\n }), \" Tiered badges for consistency, accuracy, and fluency with shareable certificates.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"3.6 Accessibility \u0026 Support\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Audio \u0026 Visual Support:\"\n }), \" Adjustable narration speed, text highlighting in sync with audio, dyslexia-friendly font toggle.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Input Options:\"\n }), \" Touch, mouse, and keyboard navigation; large hit-targets for younger learners.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Help \u0026 Guidance:\"\n }), \" Parent tips embedded in each lesson; contextual help icon linking to how-to videos.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"4. Non-Functional Requirements\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Performance:\"\n }), \" Lesson screens load in under 2 seconds on mid-range tablets; assets pre-fetched to minimize delays.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Reliability:\"\n }), \" Autosave progress checkpoints; resume from last completed component if connection drops.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Security \u0026 Privacy:\"\n }), \" COPPA and GDPR-K compliant; parental consent workflow; secure storage of child data.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Offline Availability:\"\n }), \" Ability to download up to one week of lessons; progress syncs when connection restores.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Cross-Platform:\"\n }), \" Responsive design for tablets and phones; native wrappers for iOS and Android.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"5. Content Architecture\"\n }), \"\\n\", _jsx(_components.h3, {\n children: \"5.1 Lesson Metadata Schema (conceptual)\"\n }), \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-json\",\n children: \"{\\n \\\"lessonId\\\": \\\"L01\\\",\\n \\\"title\\\": \\\"Sam Sat\\\",\\n \\\"phonicsFocus\\\": [\\\"/a/\\\", \\\"m\\\", \\\"s\\\", \\\"t\\\"],\\n \\\"highFrequencyWord\\\": \\\"the\\\",\\n \\\"decodableWords\\\": [\\\"mat\\\", \\\"sat\\\", \\\"Sam\\\"],\\n \\\"story\\\": {\\n \\\"title\\\": \\\"Sam Sat\\\",\\n \\\"sentences\\\": [\\\"Sam sat on the mat.\\\"],\\n \\\"comprehensionQuestions\\\": [\\n {\\n \\\"prompt\\\": \\\"Who sat on the mat?\\\",\\n \\\"answerChoices\\\": [\\\"Sam\\\", \\\"Dad\\\", \\\"Cat\\\"],\\n \\\"correctAnswer\\\": \\\"Sam\\\"\\n }\\n ]\\n },\\n \\\"activities\\\": {\\n \\\"warmUp\\\": \\\"Say map. Change /m/ to /s/ â sap.\\\",\\n \\\"phonics\\\": \\\"Introduce letter-sound correspondences for m, s, t, a.\\\",\\n \\\"wordWork\\\": [\\\"mat\\\", \\\"sat\\\", \\\"Sam\\\"],\\n \\\"highFrequency\\\": \\\"the\\\",\\n \\\"connectedReading\\\": \\\"Sam sat on the mat.\\\"\\n }\\n}\\n\"\n })\n }), \"\\n\", _jsx(_components.h3, {\n children: \"5.2 Asset Types\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Audio:\"\n }), \" Narration, phoneme articulation, sound effects.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Visual:\"\n }), \" Character art, story illustrations, manipulatives, badge icons.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Data:\"\n }), \" JSON lesson definitions, progress states, badge inventories.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"6. Lesson Roadmap (First 10 Lessons)\"\n }), \"\\n\", _jsxs(_components.table, {\n children: [_jsx(_components.thead, {\n children: _jsxs(_components.tr, {\n children: [_jsx(_components.th, {\n children: \"Lesson\"\n }), _jsx(_components.th, {\n children: \"Title\"\n }), _jsx(_components.th, {\n children: \"Phonics Focus\"\n }), _jsx(_components.th, {\n children: \"High-Frequency Word\"\n }), _jsx(_components.th, {\n children: \"Decodable Words\"\n }), _jsx(_components.th, {\n children: \"Story Summary\"\n })]\n })\n }), _jsxs(_components.tbody, {\n children: [_jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"1\"\n }), _jsx(_components.td, {\n children: \"Sam Sat\"\n }), _jsx(_components.td, {\n children: \"/a/ short a; m, s, t\"\n }), _jsx(_components.td, {\n children: \"the\"\n }), _jsx(_components.td, {\n children: \"mat, sat, Sam\"\n }), _jsx(_components.td, {\n children: \"Sam sits on a mat.\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"2\"\n }), _jsx(_components.td, {\n children: \"A Man and a Pan\"\n }), _jsx(_components.td, {\n children: \"+n, p\"\n }), _jsx(_components.td, {\n children: \"a\"\n }), _jsx(_components.td, {\n children: \"man, pan, nap\"\n }), _jsx(_components.td, {\n children: \"A man uses a pan.\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"3\"\n }), _jsx(_components.td, {\n children: \"Dad is Sad\"\n }), _jsx(_components.td, {\n children: \"+d\"\n }), _jsx(_components.td, {\n children: \"I\"\n }), _jsx(_components.td, {\n children: \"dad, sad, mad\"\n }), _jsx(_components.td, {\n children: \"Dad and the narrator feel sad.\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"4\"\n }), _jsx(_components.td, {\n children: \"The Rag\"\n }), _jsx(_components.td, {\n children: \"+r, g\"\n }), _jsx(_components.td, {\n children: \"to\"\n }), _jsx(_components.td, {\n children: \"rag, ran, tag\"\n }), _jsx(_components.td, {\n children: \"Sam runs to get a rag.\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"5\"\n }), _jsx(_components.td, {\n children: \"A Big Bag\"\n }), _jsx(_components.td, {\n children: \"Review short a set\"\n }), _jsx(_components.td, {\n children: \"go\"\n }), _jsx(_components.td, {\n children: \"fan, bag, cat\"\n }), _jsx(_components.td, {\n children: \"A cat hides in a bag.\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"6\"\n }), _jsx(_components.td, {\n children: \"It is a Lid\"\n }), _jsx(_components.td, {\n children: \"Short i introduction; i, l\"\n }), _jsx(_components.td, {\n children: \"is\"\n }), _jsx(_components.td, {\n children: \"sit, lip, lid\"\n }), _jsx(_components.td, {\n children: \"Naming an object as a lid.\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"7\"\n }), _jsx(_components.td, {\n children: \"Him and His Hat\"\n }), _jsx(_components.td, {\n children: \"+h, b\"\n }), _jsx(_components.td, {\n children: \"in\"\n }), _jsx(_components.td, {\n children: \"hid, bit, him\"\n }), _jsx(_components.td, {\n children: \"A boy hides his hat in a bin.\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"8\"\n }), _jsx(_components.td, {\n children: \"A Kid on a Mat\"\n }), _jsx(_components.td, {\n children: \"+f, k\"\n }), _jsx(_components.td, {\n children: \"on\"\n }), _jsx(_components.td, {\n children: \"fin, kid, kit\"\n }), _jsx(_components.td, {\n children: \"A kid and a mat adventure.\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"9\"\n }), _jsx(_components.td, {\n children: \"Cat on the Cot\"\n }), _jsx(_components.td, {\n children: \"Short o introduction; c, o\"\n }), _jsx(_components.td, {\n children: \"at\"\n }), _jsx(_components.td, {\n children: \"cot, cop, cat\"\n }), _jsx(_components.td, {\n children: \"A cat rests on a cot.\"\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: \"10\"\n }), _jsx(_components.td, {\n children: \"We Can Sit\"\n }), _jsx(_components.td, {\n children: \"Review short a/i/o blends\"\n }), _jsx(_components.td, {\n children: \"we\"\n }), _jsx(_components.td, {\n children: \"mix, can, top\"\n }), _jsx(_components.td, {\n children: \"Friends sit together.\"\n })]\n })]\n })]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"7. Detailed Lesson Designs\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"Each lesson follows the same five-part structure with tailored activities, vocabulary, and comprehension checks.\"\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 1 â Sam Sat (L01)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up (Phonemic Awareness):\"\n }), \" Say \", _jsx(_components.em, {\n children: \"map\"\n }), \". Change /m/ to /s/ to make \", _jsx(_components.em, {\n children: \"sap\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instru
94ction:\"\n }), \" Introduce letter-sound correspondences for \", _jsx(_components.em, {\n children: \"m, s, t, a\"\n }), \" using sound cards, articulation videos, and tracing animations.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Blend and segment \", _jsx(_components.em, {\n children: \"mat, sat, Sam\"\n }), \" using digital letter tiles and âtap and swipeâ blending board.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word Practice:\"\n }), \" Teach \", _jsx(_components.em, {\n children: \"the\"\n }), \" with skywriting, rainbow tracing, and sentence building.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }), \" Decodable story âSam Satâ with audio support, highlight-on-read, and comprehension question: âWho sat on the mat?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 2 â A Man and a Pan (L02)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up:\"\n }), \" Clap when two words rhyme (e.g., \", _jsx(_components.em, {\n children: \"man, pan\"\n }), \" vs. \", _jsx(_components.em, {\n children: \"dog\"\n }), \").\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instruction:\"\n }), \" Add letters \", _jsx(_components.em, {\n children: \"n\"\n }), \" and \", _jsx(_components.em, {\n children: \"p\"\n }), \"; practice articulating /n/ and /p/; air-write the letters.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Build \", _jsx(_components.em, {\n children: \"man, pan, nap\"\n }), \"; dictation activity typing or selecting letter tiles to spell words.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word:\"\n }), \" Introduce \", _jsx(_components.em, {\n children: \"a\"\n }), \"; use phrase practice (âa man,â âa panâ).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }), \" Story âA Man and a Pan.â Comprehension question: âWhat does the man have?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 3 â Dad is Sad (L03)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up:\"\n }), \" Say \", _jsx(_components.em, {\n children: \"mad\"\n }), \". Change /m/ to /d/ to make \", _jsx(_components.em, {\n children: \"dad\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instruction:\"\n }), \" Introduce letter \", _jsx(_components.em, {\n children: \"d\"\n }), \" with mouth formation video and handwriting practice.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Blend \", _jsx(_components.em, {\n children: \"sad, mad, dad\"\n }), \"; rapid-fire decoding game where learners tap the spoken word.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word:\"\n }), \" Teach \", _jsx(_components.em, {\n children: \"I\"\n }), \" with sentence stems (âI am ___â).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }
94), \" Story âDad is Sad.â Comprehension: âWho is sad?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 4 â The Rag (L04)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up:\"\n }), \" Identify the word without the short a sound among \", _jsx(_components.em, {\n children: \"rag, ran, dog\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instruction:\"\n }), \" Add \", _jsx(_components.em, {\n children: \"r\"\n }), \" and \", _jsx(_components.em, {\n children: \"g\"\n }), \"; students trace letter paths while hearing articulation cues.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Segment and blend \", _jsx(_components.em, {\n children: \"rag, ran, tag\"\n }), \"; word sort by initial consonant.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word:\"\n }), \" Teach \", _jsx(_components.em, {\n children: \"to\"\n }), \"; build phrases (âto the ragâ).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }), \" Story âThe Rag.â Comprehension: âWhere did Sam run?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 5 â A Big Bag (L05)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up:\"\n }), \" Clap when you hear /a/ in \", _jsx(_components.em, {\n children: \"bag, fan, cat\"\n }), \" vs. \", _jsx(_components.em, {\n children: \"pig\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instruction:\"\n }), \" Review all short a sounds introduced in Lessons 1â4 with cumulative blending board.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Decode and encode \", _jsx(_components.em, {\n children: \"fan, bag, cat\"\n }), \"; timed fluency strips for short a words.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word:\"\n }), \" Review \", _jsx(_components.em, {\n children: \"the, a, I, to\"\n }), \" and introduce \", _jsx(_components.em, {\n children: \"go\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }), \" Story âA Big Bag.â Comprehension: âWhat is in the bag?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 6 â It is a Lid (L06)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up:\"\n }), \" Say \", _jsx(_components.em, {\n children: \"lip\"\n }), \". Change /l/ to /s/ to make \", _jsx(_components.em, {\n children: \"sip\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instruction:\"\n }), \" Introduce short i with letters \", _jsx(_components.em, {\n children: \"i\"\n }), \" and \", _jsx(_components.em, {\n children: \"l\"\n }), \"; mouth position video and tactile tracing.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Blend \", _jsx(_components.em, {\n children: \"sit, lip, lid\"\n }), \"; push sound buttons to segment words.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word:\"\n }), \" Teach \", _jsx(_components.em, {\n children: \"is\"\n }), \"; sentence practice (âIt is ___â).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }), \" Story âIt is a Lid.â Comprehension: âWhat is it?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 7 â Him and His Hat (L07)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up:\"\n }), \" Identify rhymes (\", _jsx(_components.em, {\n children: \"him, dim, rim\"\n }), \").\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instru
94ction:\"\n }), \" Add \", _jsx(_components.em, {\n children: \"h\"\n }), \" and \", _jsx(_components.em, {\n children: \"b\"\n }), \"; practice breathy /h/ and voiced /b/ sounds.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Decode \", _jsx(_components.em, {\n children: \"hid, bit, him\"\n }), \"; digital magnetic board to build words.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word:\"\n }), \" Teach \", _jsx(_components.em, {\n children: \"in\"\n }), \"; phrase practice (âin a binâ).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }), \" Story âHim and His Hat.â Comprehension: âWhere did he hide the hat?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 8 â A Kid on a Mat (L08)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up:\"\n }), \" Say \", _jsx(_components.em, {\n children: \"kit\"\n }), \". Change /t/ to /d/ to make \", _jsx(_components.em, {\n children: \"kid\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instruction:\"\n }), \" Add \", _jsx(_components.em, {\n children: \"f\"\n }), \" and \", _jsx(_components.em, {\n children: \"k\"\n }), \"; letter formation tracing with guided strokes.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Blend \", _jsx(_components.em, {\n children: \"fin, kid, kit\"\n }), \"; real vs. nonsense word sort using introduced graphemes.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word:\"\n }), \" Teach \", _jsx(_components.em, {\n children: \"on\"\n }), \" with prepositional phrase practice.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }), \" Story âA Kid on a Mat.â Comprehension: âWho is on the mat?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 9 â Cat on the Cot (L09)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up:\"\n }), \" Which word has the /o/ sound? (\", _jsx(_components.em, {\n children: \"cot, cat, sit\"\n }), \").\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instruction:\"\n }), \" Introduce short o with letters \", _jsx(_components.em, {\n children: \"c\"\n }), \" and \", _jsx(_components.em, {\n children: \"o\"\n }), \"; letter tracing with sand animation.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Decode \", _jsx(_components.em, {\n children: \"cot, cop, cat\"\n }), \"; switch-it activity replacing initial consonants.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word:\"\n }), \" Review prior words and introduce \", _jsx(_components.em, {\n children: \"at\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }), \" Story âCat on the Cot.â Comprehension: âWhere is the cat?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Lesson 10 â We Can Sit (L10)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Warm-Up:\"\n }), \" Say \", _jsx(_components.em, {\n children: \"mix\"\n }), \". Change /m/ to /f/ to make \", _jsx(_components.em, {\n children: \"fix\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Phonics Instru
94ction:\"\n }), \" Spiral review of short a, i, o with mixed consonants; emphasis on automaticity.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Word Work:\"\n }), \" Decode \", _jsx(_components.em, {\n children: \"mix, can, top\"\n }), \"; timed blending game measuring words per minute.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"High-Frequency Word:\"\n }), \" Introduce \", _jsx(_components.em, {\n children: \"we\"\n }), \"; collaborative sentence building.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Connected Reading:\"\n }), \" Story âWe Can Sit.â Comprehension: âWhat can we do?â\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"8. Assessment \u0026 Analytics\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"In-Lesson Checks:\"\n }), \" Quick tap/click responses recorded for each activity; accuracy per phoneme and word tracked.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"End-of-Lesson Review:\"\n }), \" 3-question quiz mixing decoding, encoding, and comprehension; determines mastery level.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Progression Logic:\"\n }), \" Unlock next lesson after completing current lesson with â¥70% accuracy; below threshold triggers reteach module.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Reporting:\"\n }), \" Parents receive weekly email digest; teachers can export CSV with student performance per lesson component.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"9. Technical Considerations\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Frontend:\"\n }), \" React + Vite + Tailwind (existing stack). Component modules for activities, narration controls, progress indicators.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"State Management:\"\n }), \" Centralized lesson state using Zustand or Redux Toolkit; persistence via local storage with cloud sync.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Backend (future):\"\n }), \" Node/Express or serverless functions managing user accounts, lesson data, analytics, and notifications.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Content Delivery:\"\n }), \" CDN hosting for audio/visual assets; caching strategies for offline playback.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"10. Future Enhancements\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Voice Recognition:\"\n }), \" Evaluate child oral reading; provide pronunciation feedback.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Adaptive Sequencing:\"\n }), \" AI-driven recommendations based on error patterns.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Parent Customization:\"\n }), \" Ability to add personal word lists or home names.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Classroom Mode:\"\n }), \" Teacher dashboards with group assignments, seating charts, and printable materials.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.hr, {}), \"\\n\", _jsx(_components.p, {\n children: \"This document provides a combined roadmap for the applicationâs functionality and the curriculum for the first ten lessons, ensuring alignment between instructional design and product development.\"\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"overviewTechnical":"## Overview\n\n# KidsRead Project Overview\n\n## What the project is\nKidsRead is a Next.js 14 application that acts as a joyful reading companion. It lets families track reading sessions, celebrate progress with photos and audio feedback, and administer lesson content from a single web experience. The app runs entirely on a Node/Next.js runtime (Heroku-friendly) and ships with built-in API routes for speech synthesis and image uploads so no separate backend service is required.\n\n## Architectural layout\n- **Next.js pages router**: The app uses the classic `src/pages` router for rapid rendering and API co-location. Public routes include the dashboard (`/`), reading workflow (`/reading`), history timeline (`/history`), and lesson admin (`/admin`). Shared chrome and navigation live in `src/components/Layout.jsx` and are applied via `_app.jsx`.\n- **Feature views**: Each route delegates to a corresponding view in `src/views/`. The views compose UI primitives, hooks, and data utilities to keep page files thin and focused on routing.\n- **State and data flow**: Reading logs are managed client-side through the `useReadingHistory` hook, which persists to `localStorage`, computes streaks/aggregates with `date-fns`, and exposes CRUD helpers consumed by the dashboard and history views. Lesson content is sourced from static JSON in `src/
94data/lessons.js` and managed with `useLessonManager`.\n- **UI system**: Visual components are built with Tailwind CSS and a set of shadcn-inspired primitives in `src/components/ui/` (buttons, cards, badges, dialogs, etc.). Icons come from `lucide-react`. Layout and typography lean on Tailwind utility classes configured in `tailwind.config.js` and global styles in `src/index.css`.\n- **Utilities and helpers**: API callers live in `src/api/` to encapsulate HTTP details for speech generation and photo uploads. Shared helpers (formatting, randomization, demo data) reside under `src/utils/` and `src/lib/`.\n\n## Implementation walk-through\n- **App shell**: `_app.jsx` wraps every page with `Layout`, sets up Tailwind styles, and keeps navigation consistent. `Layout.jsx` renders the top navigation, active route highlighting, and a responsive content container.\n- **Dashboard (`/`)**: `Dashboard.jsx` surfaces streak metrics, calendar-driven log selection, recent celebrations, and an inline `LogEditor` modal. It pulls computed stats (`minutesThisWeek`, `currentStreak`, `longestStreak`, etc.) directly from `useReadingHistory`.\n- **Reading flow (`/reading`)**: `Reading.jsx` guides a single reading session with lesson details, timers, and a capture form. It can post photo evidence and speech feedback through the shared API utilities.\n- **History (`/history`)**: `History.jsx` renders the chronological feed of log entries with filters and summary badges, again powered by `useReadingHistory` state.\n- **Admin (`/admin`)**: `Admin.jsx` exposes lesson editing, visibility toggles, and ordering controls using `useLessonManager`, which manages the lesson list in memory for quick adjustments.\n- **Styling**: `src/index.css` and `App.css` provide Tailwind base layers plus custom gradients, card shadows, and responsive tweaks. `tailwind.config.js` registers design tokens, font sizes, and animation keyframes for the UI kit.\n\n## API surface (Next.js API routes)\n- `POST /api/speech` (`src/pages/api/speech.js`)\n - **Purpose**: Streams OpenAI Text-to-Speech (gpt-4o-mini-tts) audio for any submitted text.\n - **Input**: JSON `{ text: string, voice?: string, format?: 'mp3' | 'opus' }`.\n - **Output**: Binary audio (`audio/mpeg` or `audio/ogg`). Returns errors with helpful hints if the endpoint is unreachable or misconfigured.\n - **Dependencies**: Uses the `openai` SDK and Node streams; requires `OPENAI_API_KEY`.\n- `POST /api/upload-photo` (`src/pages/api/upload-photo.js`)\n - **Purpose**: Accepts base64 image uploads and saves them to `public/uploads` on the Next.js server.\n - **Input**: JSON `{ fileName?: string, dataUrl: 'data:\u003cmime\u003e;base64,\u003cpayload\u003e' }`.\n - **Output**: JSON `{ url: string }` pointing to the saved file for immediate rendering.\n - **Notes**: Uses Node `fs/promises` and `path` to ensure the upload directory exists and to sanitize filenames.\n\n## Client-side API helpers\n- `fetchSpeechAudio` in `src/api/speech.js` posts to `/api/speech` (or `NEXT_PUBLIC_SPEECH_API_ENDPOINT` override) and returns an object URL + MIME type for playback, with detailed error hints for routing/hosting issues.\n- `uploadPhoto` in `src/api/uploadPhoto.js` posts base64 payloads to `/api/upload-photo` and returns the served URL so views can immediately render the new image.\n\n## Key libraries and why they are used\n- **Next.js \u0026amp; React 18**: Core app framework and rendering.\n- **Tailwind CSS + tailwindcss-animate**: Utility-first styling and lightweight animations.\n- **shadcn/ui primitives (Radix UI + class-variance-authority + tailwind-merge)**: Accessible composable UI building blocks.\n- **lucide-react**: Iconography throughout the dashboard and reading flows.\n- **date-fns**: Date math for streaks, calendar highlights, and session summaries.\n- **react-hook-form \u0026amp; zod**: Form handling and validation within editors and admin tools.\n- **OpenAI SDK**: Server-side speech generation in the `/api/speech` route.\n- **embla-carousel-react, framer-motion, recharts**: Interactive UI affordances (carousels, animations, charts) used across the views.\n\n## Deployment notes\n- The app builds with `npm run build` and runs with `npm run start` on the default Next.js port. Heroku deployment only needs a Node runtime and the `OPENAI_API_KEY` environment variable. No Netlify-specific files remain, and API routes are hosted by the Next.js server alongside the frontend.\n\n## Technical Details\n\n# KidsRead - Next.js\n\nKidsRead is now a Next.js application with built-in API routes for speech synthesis and photo uploads. The app ships with a dashboard, reading workflow, history tracking, and an admin view for lessons.\n\n## Running locally\n\n```bash\nnpm install\nnpm run dev\n```\n\nThe app runs on the default Next.js port (3000). API routes live under `/api/*` and run in the same server process.\n\n### Environment variables\n\n| Variable | Purpose |\n| --- | --- |\n| `OPENAI_API_KEY` | Required for the `/api/speech` route that streams speech audio from OpenAI. |\n| `NEXT_PUBLIC_SPEECH_API_ENDPOINT` | Optional override for the speech endpoint. Defaults to `/api/speech`. |\n\n### Speech service\n\nThe `/api/speech` route calls OpenAI's `gpt-4o-mini-tts` model and returns binary audio. If you point `NEXT_PUBLIC_SPEECH_API_ENDPOINT` to a hosted endpoint, make sure that route accepts the same payload `{ text, voice, format }
94` and returns audio bytes.\n\n### Photo uploads\n\nPhoto uploads now post to `/api/upload-photo`. The route accepts a `{ fileName, dataUrl }` payload, writes the decoded bytes to `public/uploads/`, and responds with a URL the UI can render. On ephemeral hosts you may want to swap this storage for object storage (S3, etc.); the handler is a single file in `src/pages/api/upload-photo.js`.\n\n## Building for production\n\n```bash\nnpm run build\nnpm run start\n```\n\nDeploy the built Next.js app to Heroku or any Node-friendly host. No Netlify-specific configuration or functions are required.\n\nFor more information and support, please contact Base44 support at [email protected].","overviewTechnicalMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n h2: \"h2\",\n h1: \"h1\",\n p: \"p\",\n ul: \"ul\",\n li: \"li\",\n strong: \"strong\",\n code: \"code\",\n pre: \"pre\",\n h3: \"h3\",\n table: \"table\",\n thead: \"thead\",\n tr: \"tr\",\n th: \"th\",\n tbody: \"tbody\",\n td: \"td\",\n a: \"a\"\n }, _provideComponents(), props.components);\n return _jsxs(_Fragment, {\n children: [_jsx(_components.h2, {\n children: \"Overview\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"KidsRead Project Overview\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"What the project is\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"KidsRead is a Next.js 14 application that acts as a joyful reading companion. It lets families track reading sessions, celebrate progress with photos and audio feedback, and administer lesson content from a single web experience. The app runs entirely on a Node/Next.js runtime (Heroku-friendly) and ships with built-in API routes for speech synthesis and image uploads so no separate backend service is required.\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Architectural layout\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Next.js pages router\"\n }), \": The app uses the classic \", _jsx(_components.code, {\n children: \"src/pages\"\n }), \" router for rapid rendering and API co-location. Public routes include the dashboard (\", _jsx(_components.code, {\n children: \"/\"\n }), \"), reading workflow (\", _jsx(_components.code, {\n children: \"/reading\"\n }), \"), history timeline (\", _jsx(_components.code, {\n children: \"/history\"\n }), \"), and lesson admin (\", _jsx(_components.code, {\n children: \"/admin\"\n }), \"). Shared chrome and navigation live in \", _jsx(_components.code, {\n children: \"src/components/Layout.jsx\"\n }), \" and are applied via \", _jsx(_components.code, {\n children: \"_app.jsx\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Feature views\"\n }), \": Each route delegates to a corresponding view in \", _jsx(_components.code, {\n children: \"src/views/\"\n }), \". The views compose UI primitives, hooks, and data utilities to keep page files thin and focused on routing.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"State and data flow\"\n }), \": Reading logs are managed client-side through the \", _jsx(_components.code, {\n children: \"useReadingHistory\"\n }), \" hook, which persists to \", _jsx(_components.code, {\n children: \"localStorage\"\n }), \", computes streaks/aggregates with \", _jsx(_components.code, {\n children: \"date-fns\"\n }), \", and exposes CRUD helpers consumed by the dashboard and history views. Lesson content is sourced from static JSON in \", _jsx(_components.c
94ode, {\n children: \"src/data/lessons.js\"\n }), \" and managed with \", _jsx(_components.code, {\n children: \"useLessonManager\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"UI system\"\n }), \": Visual components are built with Tailwind CSS and a set of shadcn-inspired primitives in \", _jsx(_components.code, {\n children: \"src/components/ui/\"\n }), \" (buttons, cards, badges, dialogs, etc.). Icons come from \", _jsx(_components.code, {\n children: \"lucide-react\"\n }), \". Layout and typography lean on Tailwind utility classes configured in \", _jsx(_components.code, {\n children: \"tailwind.config.js\"\n }), \" and global styles in \", _jsx(_components.code, {\n children: \"src/index.css\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Utilities and helpers\"\n }), \": API callers live in \", _jsx(_components.code, {\n children: \"src/api/\"\n }), \" to encapsulate HTTP details for speech generation and photo uploads. Shared helpers (formatting, randomization, demo data) reside under \", _jsx(_components.code, {\n children: \"src/utils/\"\n }), \" and \", _jsx(_components.code, {\n children: \"src/lib/\"\n }), \".\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Implementation walk-through\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"App shell\"\n }), \": \", _jsx(_components.code, {\n children: \"_app.jsx\"\n }), \" wraps every page with \", _jsx(_components.code, {\n children: \"Layout\"\n }), \", sets up Tailwind styles, and keeps navigation consistent. \", _jsx(_components.code, {\n children: \"Layout.jsx\"\n }), \" renders the top navigation, active route highlighting, and a responsive content container.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsxs(_components.strong, {\n children: [\"Dashboard (\", _jsx(_components.code, {\n children: \"/\"\n }), \")\"]\n }), \": \", _jsx(_components.code, {\n children: \"Dashboard.jsx\"\n }), \" surfaces streak metrics, calendar-driven log selection, recent celebrations, and an inline \", _jsx(_components.code, {\n children: \"LogEditor\"\n }), \" modal. It pulls computed stats (\", _jsx(_components.code, {\n children: \"minutesThisWeek\"\n }), \", \", _jsx(_components.code, {\n children: \"currentStreak\"\n }), \", \", _jsx(_components.code, {\n children: \"longestStreak\"\n }), \", etc.) directly from \", _jsx(_components.code, {\n children: \"useReadingHistory\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsxs(_components.strong, {\n children: [\"Reading flow (\", _jsx(_components.code, {\n children: \"/reading\"\n }), \")\"]\n }), \": \", _jsx(_components.code, {\n children: \"Reading.jsx\"\n }), \" guides a single reading session with lesson details, timers, and a capture form. It can post photo evidence and speech feedback through the shared API utilities.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsxs(_components.strong, {\n children: [\"History (\", _jsx(_components.code, {\n children: \"/history\"\n }), \")\"]\n }), \": \", _jsx(_components.code, {\n children: \"History.jsx\"\n }), \" renders the chronological feed of log entries with filters and summary badges, again powered by \", _jsx(_components.code, {\n children: \"useReadingHistory\"\n }), \" state.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsxs(_components.strong, {\n children: [\"Admin (\", _jsx(_components.c
94ode, {\n children: \"/admin\"\n }), \")\"]\n }), \": \", _jsx(_components.code, {\n children: \"Admin.jsx\"\n }), \" exposes lesson editing, visibility toggles, and ordering controls using \", _jsx(_components.code, {\n children: \"useLessonManager\"\n }), \", which manages the lesson list in memory for quick adjustments.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Styling\"\n }), \": \", _jsx(_components.code, {\n children: \"src/index.css\"\n }), \" and \", _jsx(_components.code, {\n children: \"App.css\"\n }), \" provide Tailwind base layers plus custom gradients, card shadows, and responsive tweaks. \", _jsx(_components.code, {\n children: \"tailwind.config.js\"\n }), \" registers design tokens, font sizes, and animation keyframes for the UI kit.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"API surface (Next.js API routes)\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"POST /api/speech\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/pages/api/speech.js\"\n }), \")\", \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Purpose\"\n }), \": Streams OpenAI Text-to-Speech (gpt-4o-mini-tts) audio for any submitted text.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Input\"\n }), \": JSON \", _jsx(_components.code, {\n children: \"{ text: string, voice?: string, format?: 'mp3' | 'opus' }\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Output\"\n }), \": Binary audio (\", _jsx(_components.code, {\n children: \"audio/mpeg\"\n }), \" or \", _jsx(_components.code, {\n children: \"audio/ogg\"\n }), \"). Returns errors with helpful hints if the endpoint is unreachable or misconfigured.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Dependencies\"\n }), \": Uses the \", _jsx(_components.code, {\n children: \"openai\"\n }), \" SDK and Node streams; requires \", _jsx(_components.code, {\n children: \"OPENAI_API_KEY\"\n }), \".\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"POST /api/upload-photo\"\n }), \" (\", _jsx(_components.code, {\n children: \"src/pages/api/upload-photo.js\"\n }), \")\", \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Purpose\"\n }), \": Accepts base64 image uploads and saves them to \", _jsx(_components.code, {\n children: \"public/uploads\"\n }), \" on the Next.js server.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Input\"\n }), \": JSON \", _jsx(_components.code, {\n children: \"{ fileName?: string, dataUrl: 'data:\u003cmime\u003e;base64,\u003cpayload\u003e' }\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Output\"\n }), \": JSON \", _jsx(_components.code, {\n children: \"{ url: string }\"\n }), \" pointing to the saved file for immediate rendering.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Notes\"\n }
94), \": Uses Node \", _jsx(_components.code, {\n children: \"fs/promises\"\n }), \" and \", _jsx(_components.code, {\n children: \"path\"\n }), \" to ensure the upload directory exists and to sanitize filenames.\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Client-side API helpers\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"fetchSpeechAudio\"\n }), \" in \", _jsx(_components.code, {\n children: \"src/api/speech.js\"\n }), \" posts to \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \" (or \", _jsx(_components.code, {\n children: \"NEXT_PUBLIC_SPEECH_API_ENDPOINT\"\n }), \" override) and returns an object URL + MIME type for playback, with detailed error hints for routing/hosting issues.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"uploadPhoto\"\n }), \" in \", _jsx(_components.code, {\n children: \"src/api/uploadPhoto.js\"\n }), \" posts base64 payloads to \", _jsx(_components.code, {\n children: \"/api/upload-photo\"\n }), \" and returns the served URL so views can immediately render the new image.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Key libraries and why they are used\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Next.js \u0026 React 18\"\n }), \": Core app framework and rendering.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Tailwind CSS + tailwindcss-animate\"\n }), \": Utility-first styling and lightweight animations.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"shadcn/ui primitives (Radix UI + class-variance-authority + tailwind-merge)\"\n }), \": Accessible composable UI building blocks.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"lucide-react\"\n }), \": Iconography throughout the dashboard and reading flows.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"date-fns\"\n }), \": Date math for streaks, calendar highlights, and session summaries.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"react-hook-form \u0026 zod\"\n }), \": Form handling and validation within editors and admin tools.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"OpenAI SDK\"\n }), \": Server-side speech generation in the \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \" route.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"embla-carousel-react, framer-motion, recharts\"\n }), \": Interactive UI affordances (carousels, animations, charts) used across the views.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Deployment notes\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"The app builds with \", _jsx(_components.code, {\n children: \"npm run build\"\n }), \" and runs with \", _jsx(_components.code, {\n children: \"npm run start\"\n }), \" on the default Next.js port. Heroku deployment only needs a Node runtime and the \", _jsx(_components.code, {\n children: \"OPENAI_API_KEY\"\n }), \" environment variable. No Netlify-specific files remain, and API routes are hosted by the Next.js server alongside the frontend.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Technical Details\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"KidsRead - Next.js\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"KidsRead is now a Next.js application with built-in API routes for speech synthesis and photo uploads. The app ships with a dashboard, reading workflow, history tracking, and an admin view for lessons.\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Running locally\"\n }), \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.c
94ode, {\n className: \"language-bash\",\n children: \"npm install\\nnpm run dev\\n\"\n })\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"The app runs on the default Next.js port (3000). API routes live under \", _jsx(_components.code, {\n children: \"/api/*\"\n }), \" and run in the same server process.\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Environment variables\"\n }), \"\\n\", _jsxs(_components.table, {\n children: [_jsx(_components.thead, {\n children: _jsxs(_components.tr, {\n children: [_jsx(_components.th, {\n children: \"Variable\"\n }), _jsx(_components.th, {\n children: \"Purpose\"\n })]\n })\n }), _jsxs(_components.tbody, {\n children: [_jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: _jsx(_components.code, {\n children: \"OPENAI_API_KEY\"\n })\n }), _jsxs(_components.td, {\n children: [\"Required for the \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \" route that streams speech audio from OpenAI.\"]\n })]\n }), _jsxs(_components.tr, {\n children: [_jsx(_components.td, {\n children: _jsx(_components.code, {\n children: \"NEXT_PUBLIC_SPEECH_API_ENDPOINT\"\n })\n }), _jsxs(_components.td, {\n children: [\"Optional override for the speech endpoint. Defaults to \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \".\"]\n })]\n })]\n })]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Speech service\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"The \", _jsx(_components.code, {\n children: \"/api/speech\"\n }), \" route calls OpenAI's \", _jsx(_components.code, {\n children: \"gpt-4o-mini-tts\"\n }), \" model and returns binary audio. If you point \", _jsx(_components.code, {\n children: \"NEXT_PUBLIC_SPEECH_API_ENDPOINT\"\n }), \" to a hosted endpoint, make sure that route accepts the same payload \", _jsx(_components.code, {\n children: \"{ text, voice, format }\"\n }), \" and returns audio bytes.\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Photo uploads\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"Photo uploads now post to \", _jsx(_components.code, {\n children: \"/api/upload-photo\"\n }), \". The route accepts a \", _jsx(_components.code, {\n children: \"{ fileName, dataUrl }\"\n }), \" payload, writes the decoded bytes to \", _jsx(_components.code, {\n children: \"public/uploads/\"\n }), \", and responds with a URL the UI can render. On ephemeral hosts you may want to swap this storage for object storage (S3, etc.); the handler is a single file in \", _jsx(_components.code, {\n children: \"src/pages/api/upload-photo.js\"\n }), \".\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Building for production\"\n }), \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm run build\\nnpm run start\\n\"\n })\n }), \"\\n\", _jsx(_components.p, {\n children: \"Deploy the built Next.js app to Heroku or any Node-friendly host. No Netlify-specific configuration or functions are required.\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"For more information and support, please contact Base44 support at \", _jsx(_components.a, {\n href: \"mailto:[email protected]\",\n children: \"[email protected]\"\n }), \".\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}}},{"id":3,"title":"Nutri Scan","descriptionSlug":"nutri-scan","tech":["Deployment : Heroku","Next.js","React","Node.js","Tailwind CSS","Radix UI","OpenAI API","USDA FoodData Central API","wger API","mysql2"],"year":"2025","link":"https://manistechmind.com","role":"Software Architect \u0026 Lead Developer","images":[{"image":"/img/nutri-3.png","description":"-","de
94scriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Nutri Scan showcase 1"},{"image":"/img/nutri-4.png","description":"-","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Nutri Scan showcase 2"},{"image":"/img/nutri-1.gif","description":"-","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Nutri Scan showcase 3"},{"image":"/img/nutri-2.gif","description":"-","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n ul: \"ul\",\n li: \"li\"\n }, _provideComponents(), props.components);\n return _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {}), \"\\n\"]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"alt":"Nutri Scan showcase 4"}],"image":"/img/nutri-3.png","description":"## Overview\n\n# Nutri Scan\n\nA Next.js application that analyzes meal photos using OpenAI GPT-4o and keeps a nutrition history in a browser-backed JSON store.\n\n## Prerequisites\n\n- Node.js 18+\n- An OpenAI API key with access to the GPT-4o model (optional for mock responses)\n- (Optional) A [USDA FoodData Central](https://fdc.nal.usda.gov/api-guide.html) API key for faster ingredient lookups\n- (Optional) A [wger](https://wger.de/en/software/api) API token for richer workout visuals and authenticated exercise lookups\n\n## Getting Started\n\n1. Install dependencies:\n ```bash\n npm install\n ```\n2. Create a `.env` file in the project root with your API keys. Server routes read `OPENAI_API_KEY`; providing\n `NEXT_PUBLIC_OPENAI_API_KEY` enables the in-browser fallback without proxying through the server.\n ```env\n OPENAI_API_KEY=sk-your-key\n # Optional: override request timeouts (ms) for OpenAI calls\n # OPENAI_REQUEST_TIMEOUT_MS=15000\n # OPENAI_ANALYSIS_TIMEOUT_MS=30000\n # Optionally override the smaller models used for typeahead + nutrition fills\n # OPENAI_SUGGESTION_MODEL=gpt-4o-mini\n # OPENAI_NUTRITION_MODEL=gpt-4o-mini\n # Optional: enables USDA-backed ingredient typeahead with caching\n USDA_API_KEY=your-fooddata-central-key\n # Optional fallback for running the client without server routes\n NEXT_PUBLIC_OPENAI_API_KEY=sk-your-key\n # Optional: enables authenticated requests for workout plans and anatomy assets\n NEXT_PUBLIC_WGER_API_KEY=your-wger-token\n ```\n If the keys are not provided the app falls back to a mock nutritional analysis and a small built-in ingredient library for testing purposes.\n3. Start the development server:\n ```bash\n npm run dev\n ```\n\n## Building for Production\n\n```bash\nnpm run build\n```\n\n## How It Works\n\n- **Image analysis**: The upload flow converts photos to base64 data URLs and proxies them through a Next.js route that invokes OpenAI GPT-4o (vision + text) for calorie and macronutrient estimates with aggressive caching and configurable timeouts to reduce first-call stalls.\n- **Storage**: Meals are stored in `src/
94data/meals.json` and synchronized to `localStorage` so the app behaves like an offline JSON database in the browser.\n- **Ingredient suggestions**: Ingredient lookups and per-ingredient calorie estimates are proxied through a Next.js API route to the OpenAI API for contextual results. When the key is missing or OpenAI cannot be reached, the UI falls back to a curated set of common foods with per-gram nutrition data.\n- **Mock mode**: When no API keys are configured, realistic mock responses are returned so the experience can be demonstrated without external calls.\n\n## Technical Details\n\n# Nutri Scan Technical Summary\n\n## Application structure\n- **Framework**: Next.js 14 App Router with React 18 client components. Shared layout, metadata, and styling live under `app/` (including `app/globals.css`).\n- **Features**: Route-to-screen mapping is centralized in `src/features/pageConfig.js`, while individual feature screens (Dashboard, Upload, History, Diet Plans, Workout Planner, Measurements, Admin) live in `src/features/`.\n- **State \u0026amp; auth**: `src/context/AuthContext.jsx` manages lightweight session/role context for protected routes; preferences like storage mode hydrate on the client via `src/lib/database/storageModeClient.js`.\n- **Styling system**: Tailwind CSS with `tailwindcss-animate`, Radix UI primitives, Lucide icons, and shadcn-inspired wrappers in `src/components/ui`. Additional UX helpers include `framer-motion`, `cmdk`, `react-resizable-panels`, `next-themes`, `sonner`, and `input-otp`.\n\n## Data \u0026amp; storage\n- **Default JSON mode**: Reads/writes meals and users in `src/data/meals.json` and `src/data/users.json`, falling back to `/tmp` if the repo path is unwritable.\n- **MySQL mode**: Optional `mysql2`-powered persistence uses `nutri_meals` and `nutri_users` tables for multi-user deployments, with normalization helpers in `src/lib/database/mysql.js`.\n- **Client cache**: Meals synchronize to localStorage so history and charts remain available offline or during API outages.\n\n## API endpoints (under `/api`)\n- `POST /api/analyze` â Accepts `{ imageDataUrl }`; returns GPT-4o-derived ingredients, macros, micronutrients, and notes.\n- `POST /api/ingredient-suggestions` â `type=\"suggestions\"` returns OpenAI/USDA-backed ingredient typeahead results; `type=\"estimate\"` returns per-ingredient calorie/macro estimates with normalized units and curated fallbacks.\n- `POST /api/upload-photo` â Validates data URLs and returns a generated key plus inline URL for attaching images client-side.\n- `GET/POST /api/meals` â Loads or persists the normalized meal store to disk or MySQL with ID and numeric coercion safeguards.\n- `GET/POST /api/users` â Reads or writes user credentials/roles with ISO timestamps and safe disk fallbacks.\n- `GET /api/wger-muscles` â Proxies the wger muscle catalog with pagination handling and one-hour caching.\n- `GET|HEAD /api/wger-asset` â Safe proxy that whitelists hosts/paths and forwards cache/content headers for anatomy assets.\n\n## Integrations and libraries\n- **OpenAI GPT-4o** for image analysis, ingredient suggestions, and per-ingredient estimates (with deterministic mocks when keys are absent).\n- **USDA FoodData Central** for ingredient lookups and nutrition fallbacks.\n- **wger API** for muscles/exercise assets that support workout planning.\n- **Charts \u0026amp; data viz**: `recharts` for nutrition/measurement charts; `date-fns` for date handling.\n- **Forms \u0026amp; validation**: `react-hook-form`, `@hookform/resolvers`, and `zod` for schema-driven validation.\n\n## Implementation highlights\n- **Nutrition services**: `src/server/nutrition.js` centralizes OpenAI calls, caching, schema validation, and unit normalization for analysis/suggestion/estimate flows.\n- **Storage mode selection**: `getStorageMode` chooses disk vs. MySQL at runtime, with helpers that sanitize payloads and normalize rows before persistence.\n- **Resilience**: Mock responses and curated ingredient catalogs keep the app demoable without API keys; API routes use timeouts, caching, and safe fallbacks to avoid UI stalls.\n- **UI composition**: Feature screens share consistent layout/navigation and brand constants from `src/constants/app.js`, while shadcn-style components keep Radix primitives themed and reusable.\n\n- Exports playbooks for capacity planning and incident drills.\n- Integrates CI checks that fail risky deployments automatically.","descriptionMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n h2: \"h2\",\n h1: \"h1\",\n p: \"p\",\n ul: \"ul\",\n li: \"li\",\n a: \"a\",\n ol: \"ol\",\n pre: \"pre\",\n code: \"code\",\n strong: \"strong\"\n }, _provideComponents(), props.components);\n return _jsxs(_Fragment, {\n children: [_jsx(_components.h2, {\n children: \"Overview\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Nutri Scan\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"A Next.js application that analyzes meal photos using OpenAI GPT-4o and keeps a nutrition history in a browser-backed JSON store.\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Prerequisites\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"Node.js 18+\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"An OpenAI API key with access to the GPT-4o model (optional for mock responses)\"\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"(Optional) A \", _jsx(_components.a, {\n href: \"https://fdc.nal.usda.gov/api-guide.html\",\n children: \"USDA FoodData Central\"\n }), \" API key for faster ingredient lookups\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"(Optional) A \", _jsx(_components.a, {\n href: \"https://wger.de/en/software/api\",\n children: \"wger\"\n }
94), \" API token for richer workout visuals and authenticated exercise lookups\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Getting Started\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Install dependencies:\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm install\\n\"\n })\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Create a \", _jsx(_components.code, {\n children: \".env\"\n }), \" file in the project root with your API keys. Server routes read \", _jsx(_components.code, {\n children: \"OPENAI_API_KEY\"\n }), \"; providing\\n\", _jsx(_components.code, {\n children: \"NEXT_PUBLIC_OPENAI_API_KEY\"\n }), \" enables the in-browser fallback without proxying through the server.\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-env\",\n children: \"OPENAI_API_KEY=sk-your-key\\n# Optional: override request timeouts (ms) for OpenAI calls\\n# OPENAI_REQUEST_TIMEOUT_MS=15000\\n# OPENAI_ANALYSIS_TIMEOUT_MS=30000\\n# Optionally override the smaller models used for typeahead + nutrition fills\\n# OPENAI_SUGGESTION_MODEL=gpt-4o-mini\\n# OPENAI_NUTRITION_MODEL=gpt-4o-mini\\n# Optional: enables USDA-backed ingredient typeahead with caching\\nUSDA_API_KEY=your-fooddata-central-key\\n# Optional fallback for running the client without server routes\\nNEXT_PUBLIC_OPENAI_API_KEY=sk-your-key\\n# Optional: enables authenticated requests for workout plans and anatomy assets\\nNEXT_PUBLIC_WGER_API_KEY=your-wger-token\\n\"\n })\n }), \"\\n\", \"If the keys are not provided the app falls back to a mock nutritional analysis and a small built-in ingredient library for testing purposes.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Start the development server:\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm run dev\\n\"\n })\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Building for Production\"\n }), \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm run build\\n\"\n })\n }), \"\\n\", _jsx(_components.h2, {\n children: \"How It Works\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Image analysis\"\n }), \": The upload flow converts photos to base64 data URLs and proxies them through a Next.js route that invokes OpenAI GPT-4o (vision + text) for calorie and macronutrient estimates with aggressive caching and configurable timeouts to reduce first-call stalls.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Storage\"\n }), \": Meals are stored in \", _jsx(_components.code, {\n children: \"src/data/meals.json\"\n }), \" and synchronized to \", _jsx(_components.code, {\n children: \"localStorage\"\n }), \" so the app behaves like an offline JSON database in the browser.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Ingredient suggestions\"\n }), \": Ingredient lookups and per-ingredient calorie estimates are proxied through a Next.js API route to the OpenAI API for contextual results. When the key is missing or OpenAI cannot be reached, the UI falls back to a curated set of common foods with per-gram nutrition data.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Mock mode\"\n }), \": When no API keys are configured, realistic mock responses are returned so the experience can be demonstrated without external
94calls.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Technical Details\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Nutri Scan Technical Summary\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Application structure\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Framework\"\n }), \": Next.js 14 App Router with React 18 client components. Shared layout, metadata, and styling live under \", _jsx(_components.code, {\n children: \"app/\"\n }), \" (including \", _jsx(_components.code, {\n children: \"app/globals.css\"\n }), \").\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Features\"\n }), \": Route-to-screen mapping is centralized in \", _jsx(_components.code, {\n children: \"src/features/pageConfig.js\"\n }), \", while individual feature screens (Dashboard, Upload, History, Diet Plans, Workout Planner, Measurements, Admin) live in \", _jsx(_components.code, {\n children: \"src/features/\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"State \u0026 auth\"\n }), \": \", _jsx(_components.code, {\n children: \"src/context/AuthContext.jsx\"\n }), \" manages lightweight session/role context for protected routes; preferences like storage mode hydrate on the client via \", _jsx(_components.code, {\n children: \"src/lib/database/storageModeClient.js\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Styling system\"\n }), \": Tailwind CSS with \", _jsx(_components.code, {\n children: \"tailwindcss-animate\"\n }), \", Radix UI primitives, Lucide icons, and shadcn-inspired wrappers in \", _jsx(_components.code, {\n children: \"src/components/ui\"\n }), \". Additional UX helpers include \", _jsx(_components.code, {\n children: \"framer-motion\"\n }), \", \", _jsx(_components.code, {\n children: \"cmdk\"\n }), \", \", _jsx(_components.code, {\n children: \"react-resizable-panels\"\n }), \", \", _jsx(_components.code, {\n children: \"next-themes\"\n }), \", \", _jsx(_components.code, {\n children: \"sonner\"\n }), \", and \", _jsx(_components.code, {\n children: \"input-otp\"\n }), \".\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Data \u0026 storage\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Default JSON mode\"\n }), \": Reads/writes meals and users in \", _jsx(_components.code, {\n children: \"src/data/meals.json\"\n }), \" and \", _jsx(_components.code, {\n children: \"src/data/users.json\"\n }), \", falling back to \", _jsx(_components.code, {\n children: \"/tmp\"\n }), \" if the repo path is unwritable.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"MySQL mode\"\n }), \": Optional \", _jsx(_components.code, {\n children: \"mysql2\"\n }), \"-powered persistence uses \", _jsx(_components.code, {\n children: \"nutri_meals\"\n }), \" and \", _jsx(_components.code, {\n children: \"nutri_users\"\n }), \" tables for multi-user deployments, with normalization helpers in \", _jsx(_components.code, {\n children: \"src/lib/database/mysql.js\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Client cache\"\n }), \": Meals synchronize to localStorage so history and charts remain available offline or during API outages.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.h2, {\n children: [\"API endpoints (under \", _jsx(_components.c
94ode, {\n children: \"/api\"\n }), \")\"]\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"POST /api/analyze\"\n }), \" â Accepts \", _jsx(_components.code, {\n children: \"{ imageDataUrl }\"\n }), \"; returns GPT-4o-derived ingredients, macros, micronutrients, and notes.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"POST /api/ingredient-suggestions\"\n }), \" â \", _jsx(_components.code, {\n children: \"type=\\\"suggestions\\\"\"\n }), \" returns OpenAI/USDA-backed ingredient typeahead results; \", _jsx(_components.code, {\n children: \"type=\\\"estimate\\\"\"\n }), \" returns per-ingredient calorie/macro estimates with normalized units and curated fallbacks.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"POST /api/upload-photo\"\n }), \" â Validates data URLs and returns a generated key plus inline URL for attaching images client-side.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"GET/POST /api/meals\"\n }), \" â Loads or persists the normalized meal store to disk or MySQL with ID and numeric coercion safeguards.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"GET/POST /api/users\"\n }), \" â Reads or writes user credentials/roles with ISO timestamps and safe disk fallbacks.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"GET /api/wger-muscles\"\n }), \" â Proxies the wger muscle catalog with pagination handling and one-hour caching.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"GET|HEAD /api/wger-asset\"\n }), \" â Safe proxy that whitelists hosts/paths and forwards cache/content headers for anatomy assets.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Integrations and libraries\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"OpenAI GPT-4o\"\n }), \" for image analysis, ingredient suggestions, and per-ingredient estimates (with deterministic mocks when keys are absent).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"USDA FoodData Central\"\n }), \" for ingredient lookups and nutrition fallbacks.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"wger API\"\n }), \" for muscles/exercise assets that support workout planning.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Charts \u0026 data viz\"\n }), \": \", _jsx(_components.code, {\n children: \"recharts\"\n }), \" for nutrition/measurement charts; \", _jsx(_components.code, {\n children: \"date-fns\"\n }), \" for date handling.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Forms \u0026 validation\"\n }), \": \", _jsx(_components.code, {\n children: \"react-hook-form\"\n }), \", \", _jsx(_components.code, {\n children: \"@hookform/resolvers\"\n }), \", and \", _jsx(_components.code, {\n children: \"zod\"\n }), \" for schema-driven validation.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Implementation highlights\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"Nutrition services\"\n }
94), \": \", _jsx(_components.code, {\n children: \"src/server/nutrition.js\"\n }), \" centralizes OpenAI calls, caching, schema validation, and unit normalization for analysis/suggestion/estimate flows.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"Storage mode selection\"\n }), \": \", _jsx(_components.code, {\n children: \"getStorageMode\"\n }), \" chooses disk vs. MySQL at runtime, with helpers that sanitize payloads and normalize rows before persistence.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"Resilience\"\n }), \": Mock responses and curated ingredient catalogs keep the app demoable without API keys; API routes use timeouts, caching, and safe fallbacks to avoid UI stalls.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"UI composition\"\n }), \": Feature screens share consistent layout/navigation and brand constants from \", _jsx(_components.code, {\n children: \"src/constants/app.js\"\n }), \", while shadcn-style components keep Radix primitives themed and reusable.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"Exports playbooks for capacity planning and incident drills.\"\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"Integrates CI checks that fail risky deployments automatically.\"\n }), \"\\n\"]\n }), \"\\n\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"requirements":"# Nutri Scan Requirements\n\nNutri Scan is a Next.js 14 (React 18) application that analyzes meal photos, predicts ingredients, and generates nutrition estimates. These requirements describe what is needed to install, configure, and operate the app in development or production.\n\n## Runtime and tooling\n- **Node.js 18+** and **npm** â required to build and run the Next.js application.\n- **Git** â recommended for cloning and version control.\n- **MySQL 8** â optional relational backing store when you need persistence beyond the bundled JSON seed files.\n- **ImageMagick** (or similar) â optional if you plan to preprocess images before sending them to the API routes.\n\n## Functional requirements\n- Users can upload or drag-and-drop a meal photo for analysis.\n- The system must extract ingredients and portion estimates using the OpenAI-based analyzer.\n- The system must provide nutrition estimates (calories, macros) per ingredient and for the whole meal.\n- Users can edit detected items, add or remove ingredients, and re-run calculations.\n- Meal history should remain available within the session and, when storage is configured, across sessions.\n- Typeahead search for ingredients should work even when external APIs are unavailable by falling back to cached data.\n- When configured with MySQL, multiple users can store and retrieve distinct meal histories.\n\n## Non-functional requirements\n- **Performance**: API responses for typical meal photos should complete within configured timeout limits.\n- **Resilience**: If an external API fails, the UI should fall back to cached data and display clear errors.\n- **Security**: API keys must be kept server-side; client-side keys are optional and used only for mock flows.\n- **Accessibility**: Forms and buttons should be keyboard navigable with descriptive labels.\n\n## Environment variables\nSet these in `.env` (server) or `.env.local` (Next.js). Required items must be provided for full functionality.\n\n### Required\n- `OPENAI_API_KEY` â enables GPT-4o image and ingredient analysis.\n\n### Optional\n- `NEXT_PUBLIC_OPENAI_API_KEY` â browser-side key for mock/fallback flows when server routes are unavailable.\n- `USDA_API_KEY` â FoodData Central key for live ingredient lookup and typeahead enrichment.\n- `NEXT_PUBLIC_WGER_API_KEY` â wger token for exercise/anatomy assets in workout planning views.\n- `OPENAI_REQUEST_TIMEOUT_MS`, `OPENAI_ANALYSIS_TIMEOUT_MS` â request timeout overrides (ms).\n- `OPENAI_SUGGESTION_MODEL`, `OPENAI_NUTRITION_MODEL` â custom OpenAI model names.\n- `DATABASE_URL` â MySQL connection string (e.g., `mysql://user:pass@host:3306/nutri`).\n\n## Installation and setup\n1. Install dependencies:\n ```bash\n npm install\n ```\n2. Run the development server (Next.js on port 3000 by default):\n ```bash\n npm run dev\n ```\n3. Build for production:\n ```bash\n npm run build\n ```\n4. Start the production server:\n ```bash\n npm start\n ```\n\n## Data and storage options\n- **Default (JSON/disk)**: Reads/writes meals and users to `src/data/*.json` with fallbacks to `/tmp` when unwritable. Suitable for local development.\n- **MySQL mode**: Uses `mysql2` helpers and tables `nutri_meals` and `nutri_users` for multi-user persistence and historical reporting. Requires `DATABASE_URL`.\n- **Client cache**: The browser keeps a
94synchronized local copy of meals for offline-friendly dashboards and history.\n\n## External services\n- **OpenAI GPT-4o** â image understanding and ingredient reasoning; required for production-grade analysis.\n- **USDA FoodData Central** â ingredient lookup/typeahead; optional but recommended for richer data.\n- **wger** â muscle catalog and anatomy assets used by workout planning visuals; optional.\n\n## Testing and quality gates\n- Linting: `npm run lint` should pass without errors.\n- Type checks: `npm run type-check` (if configured) should pass in CI.\n- Unit/integration tests: `npm test` should succeed for core flows (image upload, analysis, history persistence).\n\n## Deployment considerations\n- Configure HTTPS termination and secure handling of all API keys.\n- Ensure the deployment target supports Node.js 18+ and can reach external services listed above.\n- When using MySQL, provision backups and migrations for `nutri_meals` and `nutri_users` tables.\n- Set appropriate request timeouts and concurrency limits for OpenAI-bound routes to avoid rate limiting.","requirementsMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n h1: \"h1\",\n p: \"p\",\n h2: \"h2\",\n ul: \"ul\",\n li: \"li\",\n strong: \"strong\",\n code: \"code\",\n h3: \"h3\",\n ol: \"ol\",\n pre: \"pre\"\n }, _provideComponents(), props.components);\n return _jsxs(_Fragment, {\n children: [_jsx(_components.h1, {\n children: \"Nutri Scan Requirements\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"Nutri Scan is a Next.js 14 (React 18) application that analyzes meal photos, predicts ingredients, and generates nutrition estimates. These requirements describe what is needed to install, configure, and operate the app in development or production.\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Runtime and tooling\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Node.js 18+\"\n }), \" and \", _jsx(_components.strong, {\n children: \"npm\"\n }), \" â required to build and run the Next.js application.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Git\"\n }), \" â recommended for cloning and version control.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"MySQL 8\"\n }), \" â optional relational backing store when you need persistence beyond the bundled JSON seed files.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"ImageMagick\"\n }), \" (or similar) â optional if you plan to preprocess images before sending them to the API routes.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Functional requirements\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"Users can upload or drag-and-drop a meal photo for analysis.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"The system must extract ingredients and portion estimates using the OpenAI-based analyzer.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"The system must provide nutrition estimates (calories, macros) per ingredient and for the whole meal.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Users can edit detected items, add or remove ingredients, and re-run calculations.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Meal history should remain available within the session and, when storage is configured, across sessions.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Typeahead search for ingredients should work even when external APIs are unavailable by falling back to cached data.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"When configured with MySQL, multiple users can store and retrieve distinct meal histories.\"\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Non-functional requirements\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Performance\"\n }), \": API responses for typical meal photos should complete within configured timeout limits.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Resilience\"\n }), \": If an external API fails, the UI should fall back to cached data and display clear errors.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Security\"\n }), \": API keys must be kept server-side; client-side keys are optional and used only for mock flows.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Accessibility\"\n }), \": Forms and buttons should be keyboard navigable with descriptive labels.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Environment variables\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"Set these in \", _jsx(_components.c
94ode, {\n children: \".env\"\n }), \" (server) or \", _jsx(_components.code, {\n children: \".env.local\"\n }), \" (Next.js). Required items must be provided for full functionality.\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Required\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"OPENAI_API_KEY\"\n }), \" â enables GPT-4o image and ingredient analysis.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h3, {\n children: \"Optional\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"NEXT_PUBLIC_OPENAI_API_KEY\"\n }), \" â browser-side key for mock/fallback flows when server routes are unavailable.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"USDA_API_KEY\"\n }), \" â FoodData Central key for live ingredient lookup and typeahead enrichment.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"NEXT_PUBLIC_WGER_API_KEY\"\n }), \" â wger token for exercise/anatomy assets in workout planning views.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"OPENAI_REQUEST_TIMEOUT_MS\"\n }), \", \", _jsx(_components.code, {\n children: \"OPENAI_ANALYSIS_TIMEOUT_MS\"\n }), \" â request timeout overrides (ms).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"OPENAI_SUGGESTION_MODEL\"\n }), \", \", _jsx(_components.code, {\n children: \"OPENAI_NUTRITION_MODEL\"\n }), \" â custom OpenAI model names.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"DATABASE_URL\"\n }), \" â MySQL connection string (e.g., \", _jsx(_components.code, {\n children: \"mysql://user:pass@host:3306/nutri\"\n }), \").\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Installation and setup\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Install dependencies:\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm install\\n\"\n })\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Run the development server (Next.js on port 3000 by default):\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm run dev\\n\"\n })\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Build for production:\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm run build\\n\"\n })\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Start the production server:\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm start\\n\"\n })\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Data and storage options\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Default (JSON/disk)\"\n }), \": Reads/writes meals and users to \", _jsx(_components.code, {\n children: \"src/data/*.json\"\n }), \" with fallbacks to \", _jsx(_components.code, {\n children: \"/tmp\"\n }), \" when unwritable. Suitable for local development.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"MySQL mode\"\n }
94), \": Uses \", _jsx(_components.code, {\n children: \"mysql2\"\n }), \" helpers and tables \", _jsx(_components.code, {\n children: \"nutri_meals\"\n }), \" and \", _jsx(_components.code, {\n children: \"nutri_users\"\n }), \" for multi-user persistence and historical reporting. Requires \", _jsx(_components.code, {\n children: \"DATABASE_URL\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Client cache\"\n }), \": The browser keeps a synchronized local copy of meals for offline-friendly dashboards and history.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"External services\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"OpenAI GPT-4o\"\n }), \" â image understanding and ingredient reasoning; required for production-grade analysis.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"USDA FoodData Central\"\n }), \" â ingredient lookup/typeahead; optional but recommended for richer data.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"wger\"\n }), \" â muscle catalog and anatomy assets used by workout planning visuals; optional.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Testing and quality gates\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Linting: \", _jsx(_components.code, {\n children: \"npm run lint\"\n }), \" should pass without errors.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Type checks: \", _jsx(_components.code, {\n children: \"npm run type-check\"\n }), \" (if configured) should pass in CI.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Unit/integration tests: \", _jsx(_components.code, {\n children: \"npm test\"\n }), \" should succeed for core flows (image upload, analysis, history persistence).\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Deployment considerations\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"Configure HTTPS termination and secure handling of all API keys.\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"Ensure the deployment target supports Node.js 18+ and can reach external services listed above.\"\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"When using MySQL, provision backups and migrations for \", _jsx(_components.code, {\n children: \"nutri_meals\"\n }), \" and \", _jsx(_components.code, {\n children: \"nutri_users\"\n }), \" tables.\"]\n }), \"\\n\", _jsx(_components.li, {\n children: \"Set appropriate request timeouts and concurrency limits for OpenAI-bound routes to avoid rate limiting.\"\n }), \"\\n\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"overviewTechnical":"## Overview\n\n# Nutri Scan\n\nA Next.js application that analyzes meal photos using OpenAI GPT-4o and keeps a nutrition history in a browser-backed JSON store.\n\n## Prerequisites\n\n- Node.js 18+\n- An OpenAI API key with access to the GPT-4o model (optional for mock responses)\n- (Optional) A [USDA FoodData Central](https://fdc.nal.usda.gov/api-guide.html) API key for faster ingredient lookups\n- (Optional) A [wger](https://wger.de/en/software/api) API token for richer workout visual
94s and authenticated exercise lookups\n\n## Getting Started\n\n1. Install dependencies:\n ```bash\n npm install\n ```\n2. Create a `.env` file in the project root with your API keys. Server routes read `OPENAI_API_KEY`; providing\n `NEXT_PUBLIC_OPENAI_API_KEY` enables the in-browser fallback without proxying through the server.\n ```env\n OPENAI_API_KEY=sk-your-key\n # Optional: override request timeouts (ms) for OpenAI calls\n # OPENAI_REQUEST_TIMEOUT_MS=15000\n # OPENAI_ANALYSIS_TIMEOUT_MS=30000\n # Optionally override the smaller models used for typeahead + nutrition fills\n # OPENAI_SUGGESTION_MODEL=gpt-4o-mini\n # OPENAI_NUTRITION_MODEL=gpt-4o-mini\n # Optional: enables USDA-backed ingredient typeahead with caching\n USDA_API_KEY=your-fooddata-central-key\n # Optional fallback for running the client without server routes\n NEXT_PUBLIC_OPENAI_API_KEY=sk-your-key\n # Optional: enables authenticated requests for workout plans and anatomy assets\n NEXT_PUBLIC_WGER_API_KEY=your-wger-token\n ```\n If the keys are not provided the app falls back to a mock nutritional analysis and a small built-in ingredient library for testing purposes.\n3. Start the development server:\n ```bash\n npm run dev\n ```\n\n## Building for Production\n\n```bash\nnpm run build\n```\n\n## How It Works\n\n- **Image analysis**: The upload flow converts photos to base64 data URLs and proxies them through a Next.js route that invokes OpenAI GPT-4o (vision + text) for calorie and macronutrient estimates with aggressive caching and configurable timeouts to reduce first-call stalls.\n- **Storage**: Meals are stored in `src/data/meals.json` and synchronized to `localStorage` so the app behaves like an offline JSON database in the browser.\n- **Ingredient suggestions**: Ingredient lookups and per-ingredient calorie estimates are proxied through a Next.js API route to the OpenAI API for contextual results. When the key is missing or OpenAI cannot be reached, the UI falls back to a curated set of common foods with per-gram nutrition data.\n- **Mock mode**: When no API keys are configured, realistic mock responses are returned so the experience can be demonstrated without external calls.\n\n## Technical Details\n\n# Nutri Scan Technical Summary\n\n## Application structure\n- **Framework**: Next.js 14 App Router with React 18 client components. Shared layout, metadata, and styling live under `app/` (including `app/globals.css`).\n- **Features**: Route-to-screen mapping is centralized in `src/features/pageConfig.js`, while individual feature screens (Dashboard, Upload, History, Diet Plans, Workout Planner, Measurements, Admin) live in `src/features/`.\n- **State \u0026amp; auth**: `src/context/AuthContext.jsx` manages lightweight session/role context for protected routes; preferences like storage mode hydrate on the client via `src/lib/database/storageModeClient.js`.\n- **Styling system**: Tailwind CSS with `tailwindcss-animate`, Radix UI primitives, Lucide icons, and shadcn-inspired wrappers in `src/components/ui`. Additional UX helpers include `framer-motion`, `cmdk`, `react-resizable-panels`, `next-themes`, `sonner`, and `input-otp`.\n\n## Data \u0026amp; storage\n- **Default JSON mode**: Reads/writes meals and users in `src/data/meals.json` and `src/data/users.json`, falling back to `/tmp` if the repo path is unwritable.\n- **MySQL mode**: Optional `mysql2`-powered persistence uses `nutri_meals` and `nutri_users` tables for multi-user deployments, with normalization helpers in `src/lib/database/mysql.js`.\n- **Client cache**: Meals synchronize to localStorage so history and charts remain available offline or during API outages.\n\n## API endpoints (under `/api`)\n- `POST /api/analyze` â Accepts `{ imageDataUrl }`; returns GPT-4o-derived ingredients, macros, micronutrients, and notes.\n- `POST /api/ingredient-suggestions` â `type=\"suggestions\"` returns OpenAI/USDA-backed ingredient typeahead results; `type=\"estimate\"` returns per-ingredient calorie/macro estimates with normalized units and curated fallbacks.\n- `POST /api/upload-photo` â Validates data URLs and returns a generated key plus inline URL for attaching images client-side.\n- `GET/POST /api/meals` â Loads or persists the normalized meal store to disk or MySQL with ID and numeric coercion safeguards.\n- `GET/POST /api/users` â Reads or writes user credentials/roles with ISO timestamps and safe disk fallbacks.\n- `GET /api/wger-muscles` â Proxies the wger muscle catalog with pagination handling and one-hour caching.\n- `GET|HEAD /api/wger-asset` â Safe proxy that whitelists hosts/paths and forwards cache/content headers for anatomy assets.\n\n## Integrations and libraries\n- **OpenAI GPT-4o** for image analysis, ingredient suggestions, and per-ingredient estimates (with deterministic mocks when keys are absent).\n- **USDA FoodData Central** for ingredient lookups and nutrition fallbacks.\n- **wger API** for muscles/exercise assets that support workout planning.\n- **Charts \u0026amp; data viz**: `recharts` for nutrition/measurement charts; `date-fns` for date handling.\n- **Forms \u0026amp; validation**: `react-hook-form`, `@hookform/resolvers`, and `zod` for schema-driven validation.\n\n## Implementation highlights\n- **Nutrition services**: `src/server/nutrition.js` centralizes OpenAI calls, caching, schema validation, and unit normalization for analysis/suggestion/estimate flows.\n- **Storage mode selection**: `getStorageMode` chooses disk vs. MySQL at runtime, with helpers that sanitize payloads and normalize rows before persistence.\n- **Resilience**: Mock responses and curated ingredient catalogs keep the app demoable without API keys; API routes use timeouts, caching, and safe fallbacks to avoid UI stalls.\n- **UI composition**: Feature screens share consistent layout/navigation and brand constants from `src/constants/app.js`, while shadcn-style components keep Radix primitives themed and reusable.\n\n- Exports playbooks for capacity planning and incident drills.\n- Integrates CI checks that fail risky deployments automatically.","overviewTechnicalMdx":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = Object.assign({\n h2: \"h2\",\n h1: \"h1\",\n p: \"p\",\n ul: \"ul\",\n li: \"li\",\n a: \"a\",\n ol: \"ol\",\n pre: \"pre\",\n code: \"code\",\n strong: \"strong\"\n }, _provideComponents(), props.components);\n return _jsxs(_Fragment, {\n children: [_jsx(_components.h2, {\n children: \"Overview\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Nutri Scan\"\n }), \"\\n\", _jsx(_components.p, {\n children: \"A Next.js application that analyzes meal photos using OpenAI GPT-4o and keeps a nutrition history in a browser-backed JSON store.\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Prerequisites\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsx(_components.li, {\n children: \"Node.js 18+\"\n }), \"\\n\", _jsx(_components.li, {\n children: \"An OpenAI API key with access to the GPT-4o model (optional for mock responses)\"\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"(Optional) A \", _jsx(_components.a, {\n href: \"https://fdc.nal.usda.gov/api-guide.html\",\n children: \"USDA FoodData Central\"\n }), \" API key for faster ingredient lookups\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"(Optional) A \", _jsx(_components.a, {\n href: \"https://wger.de/en/software/api\",\n children: \"wger\"\n }
94), \" API token for richer workout visuals and authenticated exercise lookups\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Getting Started\"\n }), \"\\n\", _jsxs(_components.ol, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"Install dependencies:\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm install\\n\"\n })\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Create a \", _jsx(_components.code, {\n children: \".env\"\n }), \" file in the project root with your API keys. Server routes read \", _jsx(_components.code, {\n children: \"OPENAI_API_KEY\"\n }), \"; providing\\n\", _jsx(_components.code, {\n children: \"NEXT_PUBLIC_OPENAI_API_KEY\"\n }), \" enables the in-browser fallback without proxying through the server.\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-env\",\n children: \"OPENAI_API_KEY=sk-your-key\\n# Optional: override request timeouts (ms) for OpenAI calls\\n# OPENAI_REQUEST_TIMEOUT_MS=15000\\n# OPENAI_ANALYSIS_TIMEOUT_MS=30000\\n# Optionally override the smaller models used for typeahead + nutrition fills\\n# OPENAI_SUGGESTION_MODEL=gpt-4o-mini\\n# OPENAI_NUTRITION_MODEL=gpt-4o-mini\\n# Optional: enables USDA-backed ingredient typeahead with caching\\nUSDA_API_KEY=your-fooddata-central-key\\n# Optional fallback for running the client without server routes\\nNEXT_PUBLIC_OPENAI_API_KEY=sk-your-key\\n# Optional: enables authenticated requests for workout plans and anatomy assets\\nNEXT_PUBLIC_WGER_API_KEY=your-wger-token\\n\"\n })\n }), \"\\n\", \"If the keys are not provided the app falls back to a mock nutritional analysis and a small built-in ingredient library for testing purposes.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"Start the development server:\", \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm run dev\\n\"\n })\n }), \"\\n\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Building for Production\"\n }), \"\\n\", _jsx(_components.pre, {\n children: _jsx(_components.code, {\n className: \"language-bash\",\n children: \"npm run build\\n\"\n })\n }), \"\\n\", _jsx(_components.h2, {\n children: \"How It Works\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Image analysis\"\n }), \": The upload flow converts photos to base64 data URLs and proxies them through a Next.js route that invokes OpenAI GPT-4o (vision + text) for calorie and macronutrient estimates with aggressive caching and configurable timeouts to reduce first-call stalls.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Storage\"\n }), \": Meals are stored in \", _jsx(_components.code, {\n children: \"src/data/meals.json\"\n }), \" and synchronized to \", _jsx(_components.code, {\n children: \"localStorage\"\n }), \" so the app behaves like an offline JSON database in the browser.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Ingredient suggestions\"\n }), \": Ingredient lookups and per-ingredient calorie estimates are proxied through a Next.js API route to the OpenAI API for contextual results. When the key is missing or OpenAI cannot be reached, the UI falls back to a curated set of common foods with per-gram nutrition data.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Mock mode\"\n }), \": When no API keys are configured, realistic mock responses are returned so the experience can be demonstrated without external
94calls.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Technical Details\"\n }), \"\\n\", _jsx(_components.h1, {\n children: \"Nutri Scan Technical Summary\"\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Application structure\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Framework\"\n }), \": Next.js 14 App Router with React 18 client components. Shared layout, metadata, and styling live under \", _jsx(_components.code, {\n children: \"app/\"\n }), \" (including \", _jsx(_components.code, {\n children: \"app/globals.css\"\n }), \").\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Features\"\n }), \": Route-to-screen mapping is centralized in \", _jsx(_components.code, {\n children: \"src/features/pageConfig.js\"\n }), \", while individual feature screens (Dashboard, Upload, History, Diet Plans, Workout Planner, Measurements, Admin) live in \", _jsx(_components.code, {\n children: \"src/features/\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"State \u0026 auth\"\n }), \": \", _jsx(_components.code, {\n children: \"src/context/AuthContext.jsx\"\n }), \" manages lightweight session/role context for protected routes; preferences like storage mode hydrate on the client via \", _jsx(_components.code, {\n children: \"src/lib/database/storageModeClient.js\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Styling system\"\n }), \": Tailwind CSS with \", _jsx(_components.code, {\n children: \"tailwindcss-animate\"\n }), \", Radix UI primitives, Lucide icons, and shadcn-inspired wrappers in \", _jsx(_components.code, {\n children: \"src/components/ui\"\n }), \". Additional UX helpers include \", _jsx(_components.code, {\n children: \"framer-motion\"\n }), \", \", _jsx(_components.code, {\n children: \"cmdk\"\n }), \", \", _jsx(_components.code, {\n children: \"react-resizable-panels\"\n }), \", \", _jsx(_components.code, {\n children: \"next-themes\"\n }), \", \", _jsx(_components.code, {\n children: \"sonner\"\n }), \", and \", _jsx(_components.code, {\n children: \"input-otp\"\n }), \".\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Data \u0026 storage\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Default JSON mode\"\n }), \": Reads/writes meals and users in \", _jsx(_components.code, {\n children: \"src/data/meals.json\"\n }), \" and \", _jsx(_components.code, {\n children: \"src/data/users.json\"\n }), \", falling back to \", _jsx(_components.code, {\n children: \"/tmp\"\n }), \" if the repo path is unwritable.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"MySQL mode\"\n }), \": Optional \", _jsx(_components.code, {\n children: \"mysql2\"\n }), \"-powered persistence uses \", _jsx(_components.code, {\n children: \"nutri_meals\"\n }), \" and \", _jsx(_components.code, {\n children: \"nutri_users\"\n }), \" tables for multi-user deployments, with normalization helpers in \", _jsx(_components.code, {\n children: \"src/lib/database/mysql.js\"\n }), \".\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Client cache\"\n }), \": Meals synchronize to localStorage so history and charts remain available offline or during API outages.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.h2, {\n children: [\"API endpoints (under \", _jsx(_components.c
94ode, {\n children: \"/api\"\n }), \")\"]\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"POST /api/analyze\"\n }), \" â Accepts \", _jsx(_components.code, {\n children: \"{ imageDataUrl }\"\n }), \"; returns GPT-4o-derived ingredients, macros, micronutrients, and notes.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"POST /api/ingredient-suggestions\"\n }), \" â \", _jsx(_components.code, {\n children: \"type=\\\"suggestions\\\"\"\n }), \" returns OpenAI/USDA-backed ingredient typeahead results; \", _jsx(_components.code, {\n children: \"type=\\\"estimate\\\"\"\n }), \" returns per-ingredient calorie/macro estimates with normalized units and curated fallbacks.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"POST /api/upload-photo\"\n }), \" â Validates data URLs and returns a generated key plus inline URL for attaching images client-side.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"GET/POST /api/meals\"\n }), \" â Loads or persists the normalized meal store to disk or MySQL with ID and numeric coercion safeguards.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"GET/POST /api/users\"\n }), \" â Reads or writes user credentials/roles with ISO timestamps and safe disk fallbacks.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"GET /api/wger-muscles\"\n }), \" â Proxies the wger muscle catalog with pagination handling and one-hour caching.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.code, {\n children: \"GET|HEAD /api/wger-asset\"\n }), \" â Safe proxy that whitelists hosts/paths and forwards cache/content headers for anatomy assets.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Integrations and libraries\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"OpenAI GPT-4o\"\n }), \" for image analysis, ingredient suggestions, and per-ingredient estimates (with deterministic mocks when keys are absent).\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"USDA FoodData Central\"\n }), \" for ingredient lookups and nutrition fallbacks.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"wger API\"\n }), \" for muscles/exercise assets that support workout planning.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Charts \u0026 data viz\"\n }), \": \", _jsx(_components.code, {\n children: \"recharts\"\n }), \" for nutrition/measurement charts; \", _jsx(_components.code, {\n children: \"date-fns\"\n }), \" for date handling.\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [_jsx(_components.strong, {\n children: \"Forms \u0026 validation\"\n }), \": \", _jsx(_components.code, {\n children: \"react-hook-form\"\n }), \", \", _jsx(_components.code, {\n children: \"@hookform/resolvers\"\n }), \", and \", _jsx(_components.code, {\n children: \"zod\"\n }), \" for schema-driven validation.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsx(_components.h2, {\n children: \"Implementation highlights\"\n }), \"\\n\", _jsxs(_components.ul, {\n children: [\"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"Nutrition services\"\n }
94), \": \", _jsx(_components.code, {\n children: \"src/server/nutrition.js\"\n }), \" centralizes OpenAI calls, caching, schema validation, and unit normalization for analysis/suggestion/estimate flows.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"Storage mode selection\"\n }), \": \", _jsx(_components.code, {\n children: \"getStorageMode\"\n }), \" chooses disk vs. MySQL at runtime, with helpers that sanitize payloads and normalize rows before persistence.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"Resilience\"\n }), \": Mock responses and curated ingredient catalogs keep the app demoable without API keys; API routes use timeouts, caching, and safe fallbacks to avoid UI stalls.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsxs(_components.p, {\n children: [_jsx(_components.strong, {\n children: \"UI composition\"\n }), \": Feature screens share consistent layout/navigation and brand constants from \", _jsx(_components.code, {\n children: \"src/constants/app.js\"\n }), \", while shadcn-style components keep Radix primitives themed and reusable.\"]\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"Exports playbooks for capacity planning and incident drills.\"\n }), \"\\n\"]\n }), \"\\n\", _jsxs(_components.li, {\n children: [\"\\n\", _jsx(_components.p, {\n children: \"Integrates CI checks that fail risky deployments automatically.\"\n }), \"\\n\"]\n }), \"\\n\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}}}]},"__N_SSG":true},"page":"/projects","query":{},"buildId":"vlzqLRnNpWT2YeDoGTKKg","isFallback":false,"gsp":true,"scriptLoader":[]}</script>
94</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.