The big picture
This is a personal portfolio that grew two extra products. All three ship from a single Next.js static export; a single FastAPI container serves all of their dynamic needs.
Portfolio + Avocado
jayaremala.comThe main site: career pages, blog, lab logs, gallery, plus Avocado — a RAG chatbot at /chat that answers questions about Jaya from the knowledge base.
gradeVITian
gradevitian.jayaremala.comA student-tools app for VIT: GPA/CGPA calculators, attendance and grade predictors, a semester planner, plus accounts, badges and a rulebook Q&A over the academic regulations PDF.
VRF Bricks
vrfbricks.jayaremala.comA real fly-ash brick yard in Kavali (the owner's father's business). Bilingual English/Telugu, WhatsApp-first conversion, a brick-quantity calculator.
How the pieces connect
GitHub Pages ──serves──▶ jayaremala.com ┐
│ same out/ folder,
nginx @ Lightsail ─┬─▶ gradevitian.jayaremala.com │ built once by CI
└─▶ vrfbricks.jayaremala.com ┘
│
└─▶ api.jayaremala.com ──▶ Docker :8000 (FastAPI)
│
/data volume ├── chroma_db/ (vectors)
├── analytics.db
├── content.db
└── gradevitian.db
↑ nightly S3 backup
There is no Node server in production. Next.js runs with output: "export", so every page is pre-rendered HTML. Anything dynamic — chat, analytics, logins, saved calculators — is a client-side fetch to the FastAPI box.
Repository root
| Path | What it is |
|---|---|
| README.md | The human-facing tour — 715 lines. System diagram, deployment topology, design notes. The most complete narrative document in the repo. |
| CLAUDE.md | The working map for AI agents: layout rules, commands, conventions, "never edit this" warnings. Shorter and more prescriptive than the README. |
| .env.example | Template for the real .env. Documents every knob: API base URL, Gemini model chain, DB paths, the fastmcp host allow-list, Search Console token. |
| .env | gitignored Real secrets. Read by infra/compose.yml for local runs. |
| .gitignore | Standard Node + Python ignores, plus out/, .next/ and the regenerable PDF-scrape intermediates under backend/data/gradevitian/_*. |
| .claude/ | Claude Code project settings (settings.local.json — permission allowlist). |
| .cursor/ .superpowers/ | Editor/agent scratch dirs, untracked. .superpowers/sdd holds spec-driven-development working files. |
| backend/ frontend/ | The two deployables. Each owns its own Dockerfile and .dockerignore. |
| scripts/ infra/ docs/ .github/ | Codegen, deployment, prose, CI. Covered below. |
Two flows that matter
Almost every "why is this file here?" question resolves into one of these two pipelines. Learn them and the layout stops being surprising.
1 · Knowledge sync — how content reaches all three surfaces
data/*.ts, imported by pages at build time.frontend/src/data/knowledge/ is overwritten on every build. So are blog.json and lab.json in the backend. Edit backend/data/knowledge/*.json (or the MDX) and re-run npm run sync.
2 · Deploy — what ships where
detect-changes runs paths-filter to decide which halves deploy.[skip ci].deploy.sh health-checks the new container on :8001, then swaps it onto :8000.backend/ FastAPI · Python 3.11 · 8,486 lines
A single FastAPI app behind api.jayaremala.com. It serves the RAG chatbot, all three surfaces' analytics, the gradeVITian product API, the content CMS, and a public MCP server — from one container.
backend/ ├── data/ │ ├── knowledge/ ← SINGLE SOURCE OF TRUTH for portfolio content │ └── gradevitian/ VIT regulations corpus + legacy comments ├── src/app/ the package (src layout, installed editable) │ ├── main.py app assembly, lifespan, CORS, MCP mount │ ├── mcp_server.py public read-only MCP │ ├── agent/ core/ db/ integrations/ obs/ rag/ routers/ ├── scripts/parse_regulations.py ├── tests/ ├── Dockerfile · pyproject.toml · runtime.txt
Run everything from backend/, never backend/src/. DB paths default to cwd-relative (./chroma_db), so a different cwd silently creates a second, empty vector store and Avocado goes blank.
backend/data/ — the knowledge base
Fifteen JSON files. Twelve are hand-edited and authoritative; two are generated; one directory is a separate corpus for gradeVITian.
| File | Controls | Status |
|---|---|---|
| knowledge/profile.json | Name, tagline, bio, obsession, previous/interested domain, location, contact links, resume URL, availability. | edit |
| knowledge/experience.json | Roles, companies, dates, bullet points. | edit |
| knowledge/education.json | Degrees, institutions, GPA, highlights. | edit |
| knowledge/projects.json | Title, description, tags, featured flag, award, source links, notes. | edit |
| knowledge/skills.json | Skill categories and their items. | edit |
| knowledge/testimonials.json | Name, designation, company, LinkedIn, quote, date, source. | edit |
| knowledge/apps.json | Live apps listed on /apps, with status (live/beta/wip/archived). | edit |
| knowledge/gallery.json | Images and captions for /gallery. | edit |
| knowledge/quotes.json | /quotes entries, categorised (Work / Life / Technology / Philosophy / Creativity / Mindset). | edit |
| knowledge/spotlights.json | Homepage spotlight cards with CTAs. | edit |
| knowledge/inbox_signals.json | Inputs for the weekly digest email. | edit |
| knowledge/blog.json | Blog index — built from the MDX files by the sync script. | generated |
| knowledge/lab.json | Lab index — same, from content/lab/*.mdx. | generated |
| gradevitian/regulations.json | Curated, citable VIT academic rules. Also copied into the frontend bundle by the sync script, so it counts as a frontend input in CI too. | edit |
| gradevitian/regulation_chunks.json | 1,273-line retrieval corpus chunked from the regulations PDF, queried by rag/gv_rulebook.py. | derived |
| gradevitian/gv_legacy_comments.json | Comments carried over from the pre-rewrite gradeVITian site; seeded into SQLite on first boot. | edit |
backend/scripts/parse_regulations.py
Regenerates the two gradeVITian regulation files from docs/gradevitian/Academic-Regulations.pdf. Needs the optional [scrape] extra (PyMuPDF) — it is never imported at runtime.
core/ · obs/ · db/
| File | Responsibility | Lines |
|---|---|---|
| core/settings.py | The pydantic-settings singleton. Holds API keys, DB paths, CORS origins, and the provider:model fallback chain (Gemini → Groq → OpenRouter, each included only if its key is set). | 101 |
| core/limiter.py | SlowAPI rate limiter keyed on the real client IP (unwraps proxy headers). | 13 |
| core/gv_auth.py | gradeVITian auth using stdlib only — no PyJWT, no bcrypt. Hand-rolled HMAC token sign/verify, PBKDF-style password hashing, password-reset tokens, and the current_user/optional_user FastAPI dependencies. | 130 |
| core/gv_moderation.py | Dependency-free moderation for student comments. Normalises obfuscation (l33tspeak, repeated chars), detects links and shouting, and escalates borderline cases to an LLM classifier. | 152 |
| obs/trace.py | A tiny context-manager tracer. Each request accumulates named stage timings, which feed the waterfall on the /system dashboard. | 40 |
db/ — three SQLite stores on the persistent volume
| File | Owns | Lines |
|---|---|---|
| db/analytics.py | The biggest DB module. Unique visitors (SHA-256 hashed IPs, never raw), geo lookup, per-model breakdowns, latency percentiles, stage timings, error records, top questions, feedback and experience ratings, page stats, lead captures, reliability. Everything the /system dashboard renders. | 772 |
| db/content.py | The admin CMS store — blog posts, lab entries, quotes. Seeds itself from JSON when empty, and can regenerate blog.json/lab.json back out so admin-authored posts reach the RAG index. | 763 |
| db/gradevitian.py | Student accounts, saved calculations, per-calculator persisted state, badges, streaks, comments with moderation status, notifications, referrals, traffic counters, admin metrics. | 605 |
| db/blog_stats.py | Blog engagement: views (unique per IP, idempotent) and claps (max 50 per IP per post). Prunes orphaned rows when a post is deleted. | 264 |
rag/ — retrieval
| File | Responsibility | Lines |
|---|---|---|
| rag/ingest.py | Builds the vector store from the knowledge JSON. Has a _build_*_documents function per content type (blog, testimonials, quotes, gallery, lab, apps, FAQ, system FAQ, inbox signals, Drive résumé). Hash-gated: re-ingests only when the JSON SHA-256 changed, cached at chroma_db/.ingest_hash. | 954 |
| rag/store.py | The retrieval engine. ChromaDB client, fastembed ONNX embeddings (BAAI/bge-base-en-v1.5 — no PyTorch), a BM25 index, RRF merge of dense + sparse results, an optional cross-encoder rerank, an LRU query cache and a warmup path. | 289 |
| rag/graph.py | A lightweight static knowledge graph. Given retrieved chunks, pulls in related skill and experience documents so an answer about a project also knows the stack and the role it came from. | 192 |
| rag/gv_rulebook.py | Completely separate retrieval over the VIT regulations — its own tokenizer and index, isolated from the portfolio corpus so a student question never retrieves Jaya's résumé. | 80 |
What /ai/chat actually does
routers/ — the HTTP surface
| Router | Prefix | Endpoints | Lines |
|---|---|---|---|
| ai.py | /ai | The heart of Avocado. /chat (non-streaming fallback), /chat/stream (SSE), /chat/agentic (visible tool-calling agent with per-step trace), /summarize, /draft, /rewrite, /followups, /lead-capture, /feedback, /warmup. Also owns HyDE, prompt assembly, capacity-error detection and the provider fallback loop. | 1205 |
| gradevitian.py | /gv | Signup/login/me, forgot- and reset-password, saved calcs, per-calculator state, page-load and visit counters, public stats, admin metrics, referrals, comments (+ moderation queue), notifications, rulebook /ask, achievements. | 437 |
| admin.py | /admin | All bearer-token gated: force re-ingest and its status, sync status, Google OAuth init/callback/revoke, Gmail digest, Calendar status and preview, Drive résumé sync, Google Docs draft import, digest preview/send, analytics pruning. | 372 |
| content.py | /content | CRUD for blog, lab and quotes. Reads are public; writes and the drafts list require the admin token. This is what the /admin editors talk to. | 294 |
| stats.py | /stats | /overview, /system (the dashboard payload), /system/traces, /visit, /experience-rating, /admin. | 213 |
| blog.py | /blog | Four endpoints: record view, record clap, per-post stats, index summary. Thin — all logic is in db/blog_stats.py. | 48 |
| tools.py | /tools | Lists and invokes the agent tool registry over HTTP. Powers the /mcp explorer page on the site. | 49 |
integrations/ · agent/ · main.py
| File | Responsibility | Lines |
|---|---|---|
| main.py | App assembly. Lifespan runs run_ingest() on a background thread so startup isn't blocked; mounts the MCP server at /mcp; applies path-aware CORS via a custom ScopedCORSMiddleware (permissive for /mcp, strict everywhere else); a MCPTrailingSlashShim fixes client path quirks; exposes /health. | 207 |
| agent/tools.py | The read-only tool registry, shared by Agent mode and MCP so both expose exactly the same surface: search_knowledge, get_profile, get_experience, get_projects, get_project, get_skills, get_education, get_now, get_resume, get_blog, get_lab, get_apps, get_app, check_availability, get_booking_link. | 320 |
| mcp_server.py | Wraps that registry as a public read-only fastmcp app. Small, because agent/tools.py already did the work. | 49 |
| integrations/google_auth.py | Shared OAuth2 token lifecycle — auth URL, code exchange, refresh, persistence, revoke. Gmail, Calendar and Drive all go through it. | 160 |
| integrations/calendar.py | Real free/busy lookup so Avocado can answer "when is he free?" honestly. Computed on a thread pool with a timeout and cached, so a slow Google call never stalls a chat reply. | 238 |
| integrations/gmail.py | Sends recruiter lead-capture intros and gradeVITian transactional mail; parses inbox recruiter signals for the digest. | 192 |
| integrations/drive.py | Syncs the résumé from Drive (so the RAG index tracks the real document) and imports Google Docs drafts into the blog editor. | 212 |
| integrations/digest.py | Builds the weekly HTML digest from analytics and decides whether it's worth sending at all. | 134 |
tests/ + build files
| File | What it does |
|---|---|
| tests/conftest.py | Puts src/ on the path and points GV_DB_PATH at a temp DB before the settings singleton is constructed. An autouse fixture blocks external calls. |
| tests/test_gradevitian.py | 17 tests — the only tested subsystem. Signup/login/me, duplicate rejection, auth gating, saved calcs, persisted calc state, traffic counters, referrals, comment moderation (integration + unit + LLM escalation), the password-reset flow, and admin-metrics token gating. |
| pyproject.toml | Deps and the ruff config (100-col). Note the deliberate fastmcp>=3.4.3,<3.5 pin — 3.4.3 added Host allow-list enforcement, and an unpinned drift once broke the deployed MCP server. |
| Dockerfile | python:3.11-slim. Pre-downloads the embedding model at build time so container startup needs no network call. Keep that model string in step with EMBED_MODEL in rag/store.py. |
| runtime.txt | Pins python-3.11.9. |
frontend/ Next.js 16 · React 19 · Tailwind 4 · ~46k lines
One app, three products. The organising principle is the feature vertical: everything belonging to one product lives together, and only genuinely content-free code sits in a shared bucket.
frontend/src/ ├── app/ routes — (portfolio) group, chat, admin, gradevitian, vrfbricks ├── components/ │ ├── ui/ generic primitives — no product content │ ├── portfolio/ chat/ blog/ lab/ system/ admin/ │ └── gradevitian/ vrfbricks/ ├── lib/ api/ content/ portfolio/ admin/ gradevitian/ vrfbricks/ + root utils ├── data/ knowledge/ (generated) + typed *.ts re-exports + per-site data ├── content/ blog/*.mdx lab/*.mdx └── proxy.ts dev-only subdomain rewrite
If a component renders product-specific content, it goes in that product's folder. It belongs in components/ui/ only if it is content-free and at least two verticals could use it.
Build configuration
| File | What it does |
|---|---|
| next.config.ts | Sets output: "export", trailingSlash, unoptimized images — and inlines NEXT_PUBLIC_BUILD_YEAR. That last one exists because new Date() in a client component is a hydration hazard on a static export: the HTML is rendered once at build, but reruns in every browser, so "© 2026" mismatches the moment the year rolls over. |
| package.json | Scripts, and the key detail: predev and prebuild both run the three codegen scripts. You never have to remember to sync — npm run dev does it. |
| postcss.config.mjs | Loads @tailwindcss/postcss. There is no tailwind.config.js — Tailwind 4 config lives in src/app/globals.css under @theme inline. |
| tsconfig.json | Strict TS, the @/* path alias to src/. |
| eslint.config.mjs | Flat config extending eslint-config-next. |
| Dockerfile / .dockerignore | Used only by infra/compose.yml for the local full stack. Production never runs a Node server. |
| CLAUDE.md / AGENTS.md / README.md | Frontend-scoped agent notes (1 and 5 lines — they defer to the root) and the Next.js starter README. |
| scripts/gen-pwa-icons.mjs | Generates the portfolio PWA icon set from src/app/icon.png — 192/512 "any", a padded 512 maskable, and a 180px apple-touch-icon. Run by hand; needs sharp. |
| scripts/gen-gv-icons.mjs | Same for gradeVITian — draws a mortarboard on the brand accent, keeping content inside the maskable safe zone. |
| src/proxy.ts | Next 16 renamed Middleware → Proxy. Rewrites gradevitian.localhost/gpa → /gradevitian/gpa so you can preview clean subdomain URLs locally. Dev only — a static export disables proxies, and nginx does this job in production. |
src/app/ — routes 12,641 lines
Root-level files (apply to every surface)
| File | What it does | Lines |
|---|---|---|
| layout.tsx | The root HTML shell. Loads nine Google fonts (Geist, Geist Mono, Source Serif 4, Playfair, EB Garamond, Roboto, Cormorant, Inter, Caveat), mounts ThemeProvider, SiteTracker and PWARegister. | 151 |
| globals.css | The design system. Tailwind 4's @theme inline block plus every custom animation and prose rule — 1,689 lines, and the single largest non-generated source file in the repo. | 1689 |
| icon.png | The favicon. App Router auto-detects it; no <link> needed. | — |
| not-found.tsx | The portfolio 404 — an interactive page, not a stub. | 252 |
| loading.tsx | Full-screen AvocadoLoader. | 5 |
| sitemap.ts / robots.ts | Metadata routes for the apex domain. The subdomains get their own static files instead. | 125 / 23 |
| feed.xml/route.ts | RSS for the blog, revalidated hourly so admin-published posts appear without a rebuild. | 112 |
| llms.txt/route.ts | A machine-readable site summary for LLM crawlers — profile, page list, blog and lab entries. Same hourly cadence as the sitemap and feed. | 153 |
(portfolio)/ — the route group
Parentheses mean the folder adds no URL segment. So (portfolio)/page.tsx is /, not /portfolio — that route does not exist. Everything in here shares a Nav + Footer layout.
| Route | Page | Lines |
|---|---|---|
| / | The homepage — a long-scroll editorial narrative composing ~25 components: hero name, doodle field, dot grid, origin story, spotlights, featured work, skills constellation, opinions, RAG pipeline card, site vitals, testimonials, contact form, signature. | 803 |
| /experience /education /projects /apps | Career and work surfaces, rendered from the knowledge JSON. | — |
| /blog · /blog/[slug] /blog/tag/[tag] | Index, post and tag index. The [slug] folder also holds opengraph-image.tsx, which generates a per-post social card at build time. | — |
| /lab · /lab/[slug] | Living build logs. Same loader shape and OG-image pattern as the blog, different voice. | — |
| /gallery /quotes /now | Supporting pages. | — |
| /system | The public observability dashboard — latency percentiles, cost, quality, reliability, trace waterfalls. | — |
| /mcp | An explorer for the public MCP server: lists the tools and lets you invoke them live. | — |
| layout.tsx / loading.tsx | The shared portfolio chrome and its loading state. | — |
Routes outside the group
| Route | What it is |
|---|---|
| /chat | Avocado, full-screen. Deliberately outside (portfolio) so it gets no nav and no footer. A mobile FAB on portfolio pages links here. |
| /admin | The token-gated content editor. page.tsx is 4,395 lines — the largest source file in the repo — and orchestrates every editor component. layout.tsx marks it no-index; google-callback/ completes the OAuth handshake. |
| /gradevitian/* | 19 routes: home, the seven calculators, /ask, rules, account, login/signup, forgot- and reset-password, feedback, privacy, terms, plus 404/, not-found.tsx and opengraph-image.tsx. |
| /vrfbricks/* | 7 routes: home, bricks, delivery, visit, why-fly-ash, plus 404/ and its own OG image. |
A static export never emits a segment's not-found.tsx as a real file. So each site also has a literal 404/page.tsx route, and nginx points error_page 404 at the HTML it produces. Similarly, opengraph-image.tsx exports extension-less, so each vhost must set default_type image/png for that exact path or no scraper renders the card.
src/data/ and src/content/
| Path | What it holds | Status |
|---|---|---|
| data/knowledge/*.json | Twelve files copied verbatim from the backend by the sync script. | generated |
| data/*.ts | Thin typed wrappers — profile.ts, projects.ts, skills.ts, experience.ts, education.ts, apps.ts, gallery.ts, quotes.ts, spotlights.ts, testimonials.ts. Each imports its JSON, declares the interface, and re-exports. This is where the untyped JSON becomes typed. | edit |
| data/gradevitian/pages.json | The single page list gradeVITian's nav, search modal and sitemap generator all read. | edit |
| data/gradevitian/regulations.json | Copy of the backend's curated regulations. | generated |
| data/vrfbricks/business.ts | Verified business facts only. Ends with a NEEDS_CONFIRMATION block; unverified fields are skipped at render time rather than guessed. | edit |
| data/vrfbricks/copy.ts | Every rendered string, in English and Telugu. Kavali is a Telugu market, so the site is genuinely bilingual, not translated as an afterthought. | edit |
| data/vrfbricks/testimonials.ts | Customer quotes, typed against the bilingual copy shape. | edit |
| content/blog/*.mdx | Three posts. Frontmatter: title, date (display), publishedAt (the sort key — set once, never changed), description, tags[]. | edit |
| content/blog/BLOGGING_GUIDE.md | The house style guide for writing posts. | edit |
| content/lab/itsjaya.mdx | A 918-line living build log for this very repo. | edit |
Callout, BlogImage and Divider are auto-injected into MDX — no import line needed in a post.
src/lib/ — logic without JSX 2,570 lines
| File | Responsibility | Lines |
|---|---|---|
| api/client.ts | The backend fetch wrapper (apiPost and friends) — base URL, headers, error shape. | 79 |
| api/content.ts | Typed calls against /content/*. Used by SWR hooks in client components and by generateStaticParams at build time. | 138 |
| content/blog.ts | The MDX loader — reads the files, parses frontmatter with gray-matter, sorts by publishedAt. | 86 |
| content/lab.ts | Same shape for lab entries. | 81 |
| portfolio/site-nav.tsx | Single source of truth for the site's navigable pages. Nav, footer, search and sitemap all derive from it. | 93 |
| portfolio/searchIndex.ts | Builds the ⌘K search index across pages, posts, projects and lab entries, with scoring. | 233 |
| portfolio/seo.ts | Canonical URLs plus the sitewide WebSite JSON-LD entity and per-section breadcrumbs. | 42 |
| portfolio/pages.ts | Page metadata used by breadcrumbs and nav. | 92 |
| admin/githubStaging.tsx | Stages knowledge-JSON edits as GitHub commits from the admin UI. | 119 |
| admin/useGitHubFile.ts | Hook to read and write one file through the GitHub API. | 63 |
| gradevitian/calc.ts | All calculator maths — pure typed ports of the original site's client-side JS. Given inputs, returns results; no state, no DOM. | 403 |
| gradevitian/seo.ts | Canonicals, per-page metadata, JSON-LD for the subdomain. | 178 |
| gradevitian/auth.ts | Auth API calls and Bearer-token storage in localStorage. | 158 |
| gradevitian/nav.tsx | The feature-page list the nav dropdowns, footer columns and home toolkit all derive from. | 71 |
| gradevitian/usePersistentCalc.ts | A useState-shaped hook that autosaves a logged-in user's calculator fields to the backend. | 53 |
| gradevitian/searchIndex.ts | Search types and scoring over pages.json. | 47 |
| gradevitian/regulations.ts | Typed access to the curated VIT rules, so calculators cite real numbers. | 37 |
| gradevitian/useGvBase.ts | The mount-point hook — returns "" on the subdomain, "/gradevitian" on the main domain. | 32 |
| gradevitian/badges.tsx | Badge definitions and their icons. | 50 |
| vrfbricks/seo.ts | Canonicals, metadata and JSON-LD — including the LocalBusiness entity. | 213 |
| vrfbricks/nav.tsx | Single source of truth for VRF navigation. | 96 |
| vrfbricks/useVrfBase.ts | The VRF mount-point hook. | 35 |
| session.ts | Chat session persistence — messages survive a reload via a JSON round-trip. | 74 |
| sound.ts | UI sound effects with a user-toggleable mute. | 77 |
| visitor.ts | A stable per-device UUID in localStorage (jaya_vid), so stats count devices rather than shared-IP networks. | 20 |
components/ui/ — shared primitives 19 files, no product content
| Component | What it does |
|---|---|
| ThemeProvider · ThemeToggle | next-themes wrapper and the light/dark control. |
| ScrollReveal · ScrollProgress ScrollToTop · ScrollAtmosphere | The scroll toolkit: reveal-on-enter, a progress bar, a back-to-top control, and an ambient background that responds to scroll position. |
| Parallax · ParallaxImage | Apple-style vertical parallax; the image variant pre-scales so no gap appears at the edges. |
| StackSection | Sticky card-stacking scroll with content scrubbing — the homepage's signature scroll mechanic. |
| SectionIndicator · PageTransition | A scroll-position indicator, and page transitions using the native View Transitions API when available. |
| HeroDotGrid · LiquidWave · SparkleIcon | Decorative primitives. The sparkle is the site's AI glyph — a four-point star with a small companion. |
| PWARegister · InstallPWA | Registers the portfolio service worker at root scope; InstallPWA is the install prompt UI. |
| JsonLd | 16 lines. Emits a structured-data script tag server-side so crawlers see it without running JS. |
| SoundToggle · MobileNoBg | The mute control, and a helper that drops heavy backgrounds on phones. |
components/portfolio/ — the main site 33 files, ~6,300 lines
| Component | What it does | Lines |
|---|---|---|
| Nav.tsx | The site header — dropdowns, mobile drawer, search trigger, theme toggle. | 449 |
| HeroDoodleField.tsx | The hero's margin: hand-written stances you can pick up and throw. The most physical piece of interaction on the site. | 533 |
| IntroScreen.tsx | The first-visit opening sequence. | 343 |
| RagPipelineModal.tsx | An explainer of how Avocado retrieves — opened from RagPipelineCard on the homepage. | 321 |
| QuotesClient.tsx | The interactive /quotes surface: filtering, categories, layout. QuotesFeed is its 30-line server shell. | 303 |
| SearchModal.tsx | ⌘K search over the index built in lib/portfolio/searchIndex.ts. | 301 |
| ContactForm.tsx | The contact form, sent via EmailJS from the browser (no server needed). | 292 |
| HopeMolecules.tsx | An ambient node field with per-axis drift parameters. | 280 |
| AvocadoChatButton.tsx | The mobile FAB that opens /chat. | 270 |
| ProjectsGrid · GalleryGrid TestimonialsCarousel | The three content grids, each driven by its knowledge JSON. | 231 · 229 · 223 |
| SkillsSection · SkillsConstellation | Two views of the same skills data — a structured list and a constellation graph. | 203 · 184 |
| McpExplorer.tsx | Powers /mcp — lists the public MCP tools and invokes them live against /tools. | 206 |
| StillRunning · SpotlightSection Chapter · Breadcrumbs · HeroName | Homepage narrative sections and the shared content-width constraint. | 186 · 161 · 183 · 121 · 120 |
| HeroStats · SiteVitals · SiteTracker | Live numbers from /stats; SiteTracker is the invisible component that records the visit. | 124 · 68 · 27 |
| OriginStory · Opinions | Two editorial chapters — why he builds, and what the work refuses to do. | 98 · 77 |
| AvocadoMark · AvocadoLoader Signature | Brand marks. The avocado is an emerald body with an amber pit; the signature draws its own rule. | 22 · 52 · 30 |
components/chat/ — Avocado 15 files, ~2,700 lines
| Component | What it does | Lines |
|---|---|---|
| ChatInterface.tsx | The orchestrator: SSE stream consumption, message state, session persistence, model-badge updates when a fallback fires, analytics pings, agent-mode switching. | 838 |
| ChatMessage.tsx | One message — markdown rendering, source citations, feedback controls. | 270 |
| NavSuggestions.tsx | Turns a reply into "go here next" links, so the chatbot routes people into the site. | 239 |
| ChatLanding.tsx | The empty state — suggested openers before the first message. | 198 |
| ChatCloseButton · ChatInput ChatToolbar | The chrome: exit control, composer, and mode/settings bar. | 172 · 166 · 133 |
| LeadCaptureCard.tsx | The recruiter capture card — posts to /ai/lead-capture, which emails an intro. | 156 |
| Tile.tsx · RichCards.tsx | The card canon as a reusable primitive, and the rich results that render inside replies. | 107 · 96 |
| LoadingGame.tsx | A small game to play while a slow reply generates. | 103 |
| AnswerTrace · AgentSteps | The transparency layer: which chunks were retrieved, and which tools the agent called. | 100 · 78 |
| BookingCard · AvocadoBg | Real calendar slots from /admin/calendar, and a single barely-there gradient wash. | 87 · 16 |
components/blog/ and components/lab/
| Component | What it does | Lines |
|---|---|---|
| BlogGuideDrawer.tsx | An in-app authoring guide surfaced beside the editor. | 659 |
| BlogIndexStats.tsx | Aggregate views and claps on the blog index, from /blog/stats/summary. | 624 |
| BlogEngagement.tsx | The per-post view/clap widget. Claps are debounced 1.5 s before sending, so holding the button costs one request, not fifty. | 238 |
| BlogImage.tsx | Auto-injected MDX image with captions and lightbox. | 196 |
| MDXComponents.tsx | The MDX component map — where Callout, BlogImage and Divider get auto-injected. | 109 |
| BlogSwitcher · TableOfContents ReadingProgress · ReadingMode FontSizeControl · ProseReveal | The reading experience: post switcher, sticky TOC, progress bar, distraction-free mode, type-size control, paragraph reveal. | 96 · 74 · 64 · 64 · 74 · 62 |
| CodeBlock · ShareButtons BlogViewCount | Shiki-highlighted code, share links, and the small view counter. | 61 · 70 · 28 |
| BlogSectionDynamic BlogPostMarkdown | The path for admin-published posts, which live in content.db as plain Markdown rather than as MDX files. | 67 · 20 |
| lab/LabList · LabMDXComponents lab/LabSectionDynamic | The lab's three components, mirroring the blog's shape with its own MDX map. | 187 · 156 · 35 |
components/system/ — the /system dashboard
Nine files, ~620 lines. SystemDashboard.tsx fetches /stats/system and composes the panels; types.ts defines the payload contract.
| Panel | Shows |
|---|---|
| LatencyPanel | p50/p95/p99 and per-stage timings. |
| TraceWaterfall | Individual request traces from obs/trace.py, drawn as a waterfall. |
| QualityPanel | Feedback ratings and answer quality signals. |
| ReliabilityStrip | Error rate and fallback frequency across the model chain. |
| CostPanel | Token spend by model. |
| PeriodToggle · Stat | The time-range control and the shared stat tile. |
components/admin/ — the editor 22 files, ~7,000 lines
Two distinct groups, and the difference matters: some editors write to GitHub (they edit the knowledge JSON, which is version-controlled), others write to the backend database (blog, lab and quotes authored in-app).
| Component | Writes to | Lines |
|---|---|---|
| LabEditor.tsx | Full-featured authoring view for living system docs — the largest editor. | 1485 |
| ContentLabEditor · ContentBlogEditor ContentQuotesEditor | content.db via /content/*. Drafts, publish, delete. | 811 · 758 · 372 |
| GalleryEditor.tsx | Images and captions, with upload handling. | 484 |
| AdminShared.tsx | The shared editor primitives — field types, list reordering, save state — used by every other editor. | 424 |
| AvailabilityEditor · NowEditor | Availability windows and the /now page. | 277 · 240 |
| SpotlightsEditor · KnowledgeBaseEditor AppsEditor · SkillsEditor KnowledgeDataView · ProjectsEditor ProfileEditor · ExperienceEditor TestimonialsEditor · EducationEditor HeroStatsEditor | The knowledge JSON, staged as GitHub commits through lib/admin/githubStaging.tsx. | 242 · 220 209 · 187 191 · 170 166 · 139 137 · 135 114 |
| GradevitianPanel.tsx | gradeVITian admin: metrics and the comment moderation queue. | 136 |
| PublishBar · StatCard | The save/publish control strip and a small metric tile. | 51 · 22 |
components/gradevitian/ 43 files, ~5,600 lines
The tools
| Component | What it calculates | Lines |
|---|---|---|
| SemesterPlanner | Plans a full semester's course load against credit rules. | 187 |
| GradePredictor | What you need on the final to land a target grade. | 149 |
| AskRulebook | The Q&A surface over the regulations — posts to /gv/ask, shows cited clauses. | 143 |
| CgpaCalculator · CgpaGoalTracker CgpaEstimator | Current CGPA, the GPA needed to hit a goal, and forward projection. | 105 · 103 · 38 |
| RulesReference | Browsable view of the curated regulations. | 96 |
| GpaCalculator · AttendanceCalculator AttendanceBar | Single-semester GPA, attendance percentage, and its visual bar. | 80 · 79 · 18 |
None of these hold the maths — every formula lives in lib/gradevitian/calc.ts as a pure function, so it is testable and shared.
Shell, accounts and growth
| Component | What it does | Lines |
|---|---|---|
| GVHome | The landing page — hero, grouped toolkit, social proof. | 516 |
| GVNav | Categorical dropdown nav, derived from lib/gradevitian/nav.tsx. | 454 |
| GVIntroScreen · GVInstall | First-visit sequence and the PWA install flow. | 281 · 272 |
| AuthForms · GVAuthProvider AccountDashboard | Login/signup/reset forms, the auth context, and the account view with saved calcs and badges. | 214 · 97 · 182 |
| GVWallOfLove · CommentsWall GVRefer | Social proof, the moderated comments wall, and the referral flow. | 136 · 90 · 110 |
| GVFooter · GVSearchModal GVStats · GVNotes · GVStats | Footer link columns, search, live usage counters, per-user notes. | 113 · 111 · 93 · 89 |
| GVLink | 14 lines, and load-bearing. Prepends the mount-point prefix to internal hrefs. Always use this, never next/link directly. | 14 |
| GVCanonicalRedirect | Bounces the path form on the main domain over to the subdomain. | 21 |
| GVFaq · GVJsonLd | A native <details> accordion that also emits matching FAQPage structured data — so what Google reads is what the visitor sees. | 46 · 16 |
| GVServiceWorker · SaveCalcButton Badges · GVResultModal · ui.tsx | PWA registration, the save-this-calculation control, badge display, result modal, and the shared UI primitives. | 30 · 58 56 · 72 · 108 |
| GVHeroTitle · GVPageHeader GVExploreMore · GVScrollTop GVSearch · GVVisits GVRedditEmbed · GVLinkedInEmbed | Presentation and cross-linking pieces. | 63 · 33 62 · 29 54 · 55 43 · 20 |
components/vrfbricks/ 19 files, ~2,900 lines
| Component | What it does | Lines |
|---|---|---|
| VRFHome | The landing page. Chaptered long-scroll, bilingual throughout. | 411 |
| VRFNav | Header with the language switch. | 414 |
| VRFField · VRFUi | Form primitives and shared UI — Section, Inner, Chapter, Headline, Eyebrow, WhatsAppButton, CallButton, TextLink. | 258 · 225 |
| VRFWhyPage · VRFBricksPage VRFDeliveryPage · VRFVisitPage | The four content pages: why fly-ash, the product range, delivery terms, and how to visit the yard. | 215 · 212 153 · 146 |
| BrickDiagram | A dimensioned axonometric drawing generated from the brick's real measurements — not an illustration, a derived drawing. | 162 |
| BrickCalculator | How many bricks a wall needs. The original site answered this nowhere, so homeowners had to guess or phone up. | 131 |
| QuoteComposer | The enquiry form is a WhatsApp message composer, not a form submission — it opens WhatsApp with the message pre-filled. | 116 |
| whatsapp.ts | Deep-link construction. WhatsApp is the primary conversion path on this site. | 70 |
| VRFLang | Language state (English ⇄ Telugu) for the whole site. | 81 |
| VRFPhoto | Photographs at their true proportions, with honest sourcing. | 109 |
| VRFFaq · VRFJsonLd | Same accordion-plus-FAQPage discipline as gradeVITian, plus the LocalBusiness entity. | 77 · 20 |
| VRFLink · VRFCanonicalRedirect | The mount-point link wrapper and the subdomain canonicaliser. | 20 · 22 |
| VRFFooter · VRFContactFab VRFTestimonials | Footer, the floating contact control on phones, and customer quotes. | 130 · 83 · 105 |
VRF Bricks is the owner's father's brick yard. Do not invent details about it, and do not caption sourced stock photography as if it documents that specific yard. Unverified facts belong in the NEEDS_CONFIRMATION block in business.ts, where they are skipped at render time.
public/ — static assets 73 files
| Path | What it holds |
|---|---|
| CNAME | Tells GitHub Pages the custom apex domain. |
| manifest.webmanifest · sw.js icon-192/512/maskable · apple-touch-icon | The portfolio PWA set, generated by scripts/gen-pwa-icons.mjs. |
| blog/ | 15 images referenced from MDX as /blog/filename.png. |
| gallery/ | 22 photos for /gallery — NYU IT, Wipro, VIT, Tailorbird, plus the system-architecture diagram. |
| gradevitian/ | The subdomain's own icon set, manifest, service worker, illustration SVGs, and robots.txt + sitemap.xml (the latter generated). nginx serves the last two at the subdomain root, because Google only reads robots.txt from an origin root. |
| vrfbricks/ | Five yard and masonry photos, plus its own robots.txt and generated sitemap.xml. |
scripts/ repo-level codegen · 366 lines
All three run automatically from the frontend's predev and prebuild hooks. You rarely invoke them by hand.
| Script | What it generates | Lines |
|---|---|---|
| sync-knowledge.mjs | The important one. Converts blog and lab MDX into backend/data/knowledge/blog.json and lab.json, then copies all knowledge JSON into frontend/src/data/knowledge/. Also copies the gradeVITian regulations across — which is why that file counts as a frontend CI input. | 137 |
| gen-gv-sitemap.mjs | Builds public/gradevitian/sitemap.xml from data/gradevitian/pages.json. <lastmod> comes from the last commit touching each route's page file; if git can't answer, the entry ships without a lastmod rather than claiming "modified today" — the signal Google learns to distrust. | 91 |
| gen-vrf-sitemap.mjs | Same discipline for VRF Bricks. | 72 |
| sync-blog.mjs | The earlier, blog-only version of the sync. Superseded by sync-knowledge.mjs, which handles blog and lab together. | 66 |
infra/ compose · nginx · deploy scripts
| File | What it does |
|---|---|
| compose.yml | The local full stack. Builds from the same Dockerfiles CI uses, so your local image matches production. Mounts backend/src and backend/data over the baked-in copies so --reload works, and uses an anonymous volume to stop the host bind-mount shadowing the image's node_modules. |
| nginx/api.jayaremala.com.conf | TLS termination for the API, proxying to 127.0.0.1:8000. Includes the ACME challenge location for cert renewal. |
| nginx/gradevitian.conf | Points the subdomain at /var/www/gv-site (the whole static export) but exposes only the /gradevitian/ segment — the portfolio's routes 404 here. Maps robots.txt, sitemap.xml and manifest.webmanifest onto the segment's copies, and sets default_type image/png for the extension-less OG image. Note the comment about add_header: nginx does not merge headers, so any location with its own add_header must repeat all of them. |
| nginx/vrfbricks.conf | The same pattern for VRF Bricks, from the same document root. |
| scripts/deploy.sh | Blue-green rollout. Restores any missing DB from S3 first, pulls the new GHCR image, starts it on :8001, health-checks it, and only then swaps it onto :8000. Tags the outgoing image :previous. |
| scripts/rollback.sh | Swaps :previous back in. Fails loudly if no previous image exists. |
| scripts/backup.sh | Nightly cron. Ships the three SQLite DBs to S3 and prunes anything older than 7 days. |
.github/workflows/
deploy.yml — five jobs, on push to main
| Job | Does |
|---|---|
| detect-changes | Runs dorny/paths-filter and outputs two booleans, frontend and backend. |
| build | Static export. Checks out with fetch-depth: 0 so the sitemap generators can read git history for <lastmod>. |
| deploy | Uploads to GitHub Pages and rsyncs out/ to Lightsail for the two subdomains. |
| sync-knowledge | Commits the regenerated JSON back to main with [skip ci], so it doesn't retrigger itself. |
| build-backend / deploy-backend | Build → push to GHCR → SSH to Lightsail → run infra/scripts/deploy.sh. |
It decides what deploys, so a missing entry means an edit silently ships nothing. Two entries exist for non-obvious reasons: Tailwind 4 has no config file, so its config is covered by frontend/src/**; and backend/data/gradevitian/regulations.json is listed under both filters, because the sync script copies it into the frontend bundle.
weekly-digest.yml
Cron at 13:00 UTC every Monday. A single curl to /admin/digest/send with the admin bearer token — the workflow holds no logic, only the schedule and the credential.
docs/
| File | What it covers | Lines |
|---|---|---|
| VRFBRICKS.md | The fullest subdomain doc: business context, the sourcing and honesty rules, bilingual copy strategy, nginx details. | 351 |
| GRADEVITIAN.md | The subdomain's architecture, mount-point mechanics, and local preview instructions. | 90 |
| gradevitian/Academic-Regulations.pdf | The source PDF backend/scripts/parse_regulations.py chunks. Kept so the corpus is reproducible. | — |
| vrfbricks-original/ | The original single-file brick-yard site, preserved as the fact-check reference for everything in business.ts. | 1816 |
| superpowers/plans/ superpowers/specs/ | Spec-driven-development artifacts from two features: the gradeVITian admin metrics work (a 568-line plan plus its design spec) and the scroll-reactive hero wordmark. | 750 |
Rules that bite
The conventions where getting it wrong fails silently rather than loudly — worth knowing before your first change.
| Rule | What happens if you don't |
|---|---|
| Edit backend knowledge JSON | Edits to frontend/src/data/knowledge/ are wiped by the next build. Same for blog.json and lab.json anywhere. |
Run the backend from backend/ | Running from backend/src/ creates a second, empty ./chroma_db. No error — Avocado just knows nothing. |
| Keep paths-filter in step | Your change deploys nothing, and CI reports success. |
Link via GVLink / VRFLink | A raw next/link breaks on whichever mount point you didn't test — subdomain or path form. |
Sort blog by publishedAt | date is an editable display string; editing it to fix a typo would silently reorder the index. |
No new Date() in client components | Hydration mismatch on a static export the moment the year rolls over. Use NEXT_PUBLIC_BUILD_YEAR. |
| Tailwind config is in globals.css | Creating a tailwind.config.js won't be read — Tailwind 4 uses @theme inline. |
Bump fastmcp deliberately | 3.4.3 added Host allow-list enforcement; an implicit bump once broke the deployed MCP server. It is pinned >=3.4.3,<3.5 on purpose. |
Keep the Dockerfile's embed model in step with rag/store.py | The image pre-downloads the model at build time. A drift means a network fetch on every cold start. |
| Don't invent VRF Bricks facts | It's a real business. Unverified details go in NEEDS_CONFIRMATION, where render skips them. |