Personalized clothing fit verdicts at the point of purchase, for Indian fashion shoppers on Myntra, AJIO, and H&M India.
Live demo · Install v0.6.0 · About this project
Apparel return rates in India sit at roughly 25-30%, and sizing is the leading driver. Every product page already shows a size chart, so the chart itself isn't the problem; trusting it is. A "36 inch bust" entry on the chart doesn't tell you whether your 34 inch bust will look snug, perfect, or roomy on this particular cut.
FitCheck doesn't replace the chart. It translates it into a verdict the shopper can act on, with a per-axis breakdown of why. The user enters their measurements once. On any supported product page, the side panel renders a recommended size with a tinted silhouette showing which axes (bust, waist, hip, length) will be snug, perfect, or loose.
The fit math has been tested against my own purchase history. A representative case from AJIO:
I ordered size 30 jeans. They arrived too loose. I shipped them back and exchanged for size 28, which fit. The chart label and my waist measurement (both "30") seemed to agree, but the actual body-chart waist value for "size 28" on that Levi's cut is 30 inches — the size labels were misleading. With FitCheck running on the same product page, the extension recommends size 28 directly. Same product, same body, would have skipped the exchange shipment.
| FitCheck recommendation on the live PDP | My actual order — exchanged 30 → 28 |
|---|---|
![]() |
![]() |
On Myntra order-history URLs (which surface a size= query param from past purchases), the side panel goes one step further and compares the recommendation against your past purchase explicitly: a green "Matches your previous order" callout when the math picks the size you actually wore, or an amber "You bought X · Math now recommends Y" when your body has shifted since. See the "Validated against past purchase" row below for examples.
Two paths to evaluate the product, depending on how much friction you're willing to accept.
Open the live demo and click through ten real product fixtures across the three retailers. Enter your measurements once; the side panel runs the real fit math against each product's actual size chart. Pure browser, nothing to install.
Use the real extension on actual Myntra / AJIO / H&M India product pages:
- Download
fitcheck-0.6.0.zipfrom the latest release. - Unzip the archive anywhere on your machine.
- Open
chrome://extensionsin Chrome. - Toggle Developer mode ON (top right).
- Click Load unpacked and select the unzipped folder.
- Pin FitCheck to your toolbar via the puzzle-piece menu.
- Open any product on Myntra, AJIO, or H&M India and click the FitCheck icon.
Three components, each scoped to one job:
- Content script + per-retailer adapters (
src/content/) read the size chart and product info from the rendered DOM. Each retailer ships data differently — Myntra exposes a hydration store, AJIO uses a Redux preload, H&M India relies on a static chart that ships as Next.js hydration metadata. The adapters normalize all of that into oneParsedProductshape. - Background service worker (
src/background/) receives the parsed product, computes the verdict against the stored user profile, caches state per tab. - Side panel React app (
src/sidepanel/) renders the verdict: recommended size, tinted silhouette, per-axis pins (bust/waist/hip/length where applicable), and a size-comparison row.
The fit math (src/lib/fit-math.ts) is the most non-obvious layer. Two paths:
- Tops & bottoms: two-axis (width + length).
- Dresses: four-axis (bust + waist + hip + length).
For tops and dresses the math is ease-aware — it knows the difference between body chart numbers ("this size fits a 34 inch bust") and garment-flat numbers ("the garment itself is 38 inches around"). The gap between the two encodes designer intent: 2 inches of ease is slim-fit, 8 inches is oversized. The verdict respects that. An oversized t-shirt on a 36 inch bust isn't "too tight" just because the chart targets a 32 inch body — it's actually loose because the garment itself is 40 inches.
For bottoms the math is body-chart-only since there's typically no ease at the waist (or even negative ease for stretch denim).
Two touchpoints sit on top of the verdict view:
- Calibrate from a known-good item. Most shoppers don't know their measurements in inches but do know "size M from AJIO fits me well." The side panel exposes a "This fits me" action that reads the body chart for the picked size and writes it into the profile, merging instead of overwriting: a top fills upper-body, a bottom fills waist/hip, calibrating from more than one item over time fills gaps. Height stays a separate input since no garment chart carries it. Only Myntra / AJIO / H&M PDPs can calibrate (we need the retailer's published body chart); the UI says so.
- Fit-outcome feedback (local). A "Did size X fit?" control records a typed
fit_outcomeevent (fit / too tight / too loose) to the local ring buffer inlib/analytics.ts. The override event already captures "disagreed at purchase"; this captures the cleaner accuracy signal — did the verdict survive contact with reality? Stays on-device; readable viareadEvents().
The verdict has three tiers that the silhouette tints accordingly:
On Myntra order-history URLs the side panel compares the math against the size you actually bought, in two states:
src/
├── background/
│ └── index.ts # Service worker — owns profile, runs fit math, caches per-tab state
├── content/
│ ├── index.ts # Content script entry — watches for SPA navigation
│ ├── adapters/
│ │ ├── types.ts # SiteAdapter interface
│ │ ├── index.ts # Adapter registry
│ │ ├── myntra.ts # Hydration-store extraction
│ │ ├── ajio.ts # __PRELOADED_STATE__ extraction
│ │ └── hm.ts # __NEXT_DATA__ + static size chart
│ └── utils/
│ ├── observer.ts # URL change observer for SPAs
│ └── extractJson.ts # Brace-counting JSON extractor (Myntra + AJIO)
├── lib/
│ ├── types.ts # Shared types across processes
│ ├── storage.ts # chrome.storage.local wrapper
│ ├── messages.ts # Typed message passing
│ ├── fit-math.ts # Per-axis verdict math
│ ├── silhouette.ts # Per-style pin layout config
│ ├── style.ts # Garment style classifier
│ ├── tint.ts # Canvas-based silhouette tinting
│ ├── hm-size-chart.ts # H&M's published size chart (static)
│ └── analytics.ts # Local event log
└── sidepanel/
├── index.html
├── main.tsx
├── App.tsx # Three-state router
├── components/
│ ├── ProfileSetup.tsx
│ ├── CalibrateProfile.tsx # Seed the profile from a known-good item
│ ├── FitVerdict.tsx
│ ├── FitIndicators.tsx # Pins + tags overlaid on silhouette
│ ├── TintedComposite.tsx # Runtime garment tinting
│ └── Fallback.tsx
└── styles.css
demo/ # Interactive standalone demo (Netlify-deployed)
├── index.html
├── main.tsx
└── src/
├── DemoApp.tsx # Chrome window mock + tab switcher
├── ChromeWindow.tsx
├── PdpMock.tsx # Per-retailer PDP near-clones
├── shims/chrome.ts # Shims chrome.* APIs onto globalThis
├── fixtures/ # 10 real product fixtures
└── styles.css
eval/ # Golden-set regression suite (npm run eval)
├── expectations.ts # (fixture, profile, expected verdict) rows
└── run.test.ts # vitest runner; imports the real computeFit
npm install
npm run build # extension build → dist/
npm run build:demo # standalone demo build → demo-dist/
npm run dev:demo # local dev server for the demo on port 5174
npm run eval # verdict regression suite (vitest)After npm run build, follow steps 3–7 of the install section above, pointing Load unpacked at the dist/ folder.
npm run eval runs a golden-set regression suite under eval/ that imports the real computeFit from src/lib/fit-math.ts and runs it against the 10 demo fixtures with locked-in expected verdicts (recommended size + tier). Flipping any expected value here fails loudly the next time someone touches the math or an adapter. Two cases lock in behaviour the README highlights: the AJIO Levi's "recommends size 28 for a 30-inch-waist body" real-world case, and the Flying Machine drop-shoulder tee "reads loose, not tight" ease-aware case. A coverage check ensures every fixture has at least one expectation.
Three steps, in order:
- Create
src/content/adapters/<retailer>.tsimplementing theSiteAdapterinterface. Seeajio.tsfor the cleanest example. - Register it in
src/content/adapters/index.ts. - Add the host to
manifest.jsonunder bothhost_permissionsandcontent_scripts.matches.
No other files need to change. The fit math, silhouettes, tinting, and side panel all consume the ParsedProduct shape — once your adapter emits a valid one, every downstream piece works automatically.
The three adapters illustrate three different data architectures, and that variety is part of the v1 narrative:
- Myntra — global hydration via
window.__myx = {...}script tag. Brace-counted JSON extraction. Has its own per-product size chart with measurements in inches. Cleanest data of the three. - AJIO — Redux preload via
window.__PRELOADED_STATE__ = {...}. Same brace-counting technique (shared util). Per-product size chart atstate.product.sizeData.sizechart[0].brickBrandSizes[], measurements in inches as strings (often with empty values for dims a brick category doesn't carry). - H&M India — Next.js hydration via
<script id="__NEXT_DATA__" type="application/json">{...}</script>. Identity and size labels come from the page; per-size body measurements do NOT — the size guide modal lazy-loads fromapi.hm.comafter render, which a content-script-only architecture cannot fetch. The adapter falls back to H&M's standardized published size chart (shipped as static data inlib/hm-size-chart.ts) for bust/waist/hip, and pulls per-product garment length fromproductAttributes.measurementwhen available.
Investigated and intentionally scoped out. Westside (Shopify-based) exposes its size chart only as a PNG image in the size-guide drawer:
<img id="sizeGuideImage"
src="//www.westside.com/cdn/shop/files/STUDIOFIT_WOMEN_TOPWEAR-CM.png"
data-inch="//www.westside.com/cdn/shop/files/STUDIOFIT_WOMEN_TOPWEAR-IN.png">There are zero <table> tags in a Westside PDP and zero machine-readable bust/waist/hip values anywhere in the rendered HTML. A content-script architecture cannot OCR PNGs at runtime without external services, and adapters MUST NOT touch the network.
Two paths to add Westside in a future iteration:
- Static-chart capture — same approach used for H&M. Capture the
STUDIOFIT_*PNGs once (women's topwear, bottomwear, dresses; men's topwear, bottomwear), transcribe the values manually into alib/westside-size-chart.tsmodule, and ship as static data. Westside's "Studio Fit" sizing has been stable. - OCR via background script — the service worker could fetch and OCR the size-guide PNG (e.g. via a Tesseract.js bundle), since the network restriction applies only to adapters running in content scripts. Larger architectural change, useful only if more than one retailer follows this pattern.
This is a documented v1 scope decision, not an oversight.
When an adapter selector breaks (a retailer ships a redesign, etc.):
- Open a PDP on the affected site.
- Open the side panel — it shows the fallback state with a brief message.
- Open
chrome://extensions, find FitCheck, click Service worker to open its DevTools. - The console shows
[FitCheck]logs including thereasonanddetailstrings from the failedExtractionResult. Each adapter's failure modes are documented at the top of its.tsfile with their exact strings — that's how you map an error back to the broken extraction path. - Update the selector / path in the adapter file. Run
npm run build, refresh the extension card.
Intentionally scoped out:
- Image-based size charts (OCR) — see Westside section above.
chrome.storage.sync(cross-device profile).- External analytics.
- A debug page for the event log.
- Mobile support.
In priority order:
- Soak test on real PDPs for all three retailers — verify silhouette/pin alignment and verdict accuracy against actual fit experience.
- Westside via static chart capture if user demand justifies it.
- A debug page to visualize the analytics events (
src/lib/analytics.tsEventtype). - First verdict-tone experiment — A/B between two phrasings of "may work" verdicts.
- Background-script SPA re-extraction — currently the side panel asks the user to refresh after profile edits or product changes; a smarter content-script observer could re-extract automatically.
FitCheck is a consumer product with a B2B endgame. The framing I build against:
- North Star: verdict-to-purchase conversion. Of shoppers who see a verdict, how many buy the recommended size. It's the metric a retailer would pay for, because it ties directly to fewer size-driven returns.
- Counter-metric: override rate. How often users pick a size other than the one recommended. The cleanest proxy for trust, and the metric most fit tools forget to instrument.
- Activation: profile completion within 24h. The single measurement entry is the make-or-break step; same-session vs 24h-plus completion is my hypothesis for the strongest predictor of week-4 retention.
Why a Chrome extension rather than, say, a Myntra-only feature: it's the only way to test the fit-verdict idea across multiple real retailers without being one of them. The proof-of-concept user is the desktop, considered-purchase shopper; the retailer-side SDK on every PDP is the everywhere endgame.
The consumer extension validates the fit-verdict hypothesis on the easiest surface, but ~60% of Indian fashion traffic is mobile, where an extension can't reach. The endgame isn't a bigger extension. It's licensing the fit engine as a retailer-side SDK that runs natively on the product page. The ladder: instrument at 1k MAU → first verdict-tone A/B at 10k → a returns-measurement pilot with one retailer (AJIO first, cleanest data) at 50k → license the engine as an SDK at 100k, with the extension as the proof point, not the product.
I built FitCheck as a portfolio piece during my transition from SAP consulting toward consumer product management. The work is end-to-end on purpose: a real extension users can install, three retailer adapters built against live page data, a fit-math layer calibrated against real Indian-retail sizing conventions, and a side-panel UI shipped with the same care as the underlying logic.
The most representative PM artifacts in this repo are the Westside scope-out, the real-world validation (ground-truth against my own returns), the PM view, and the post-v1 roadmap. Together they show how I think about saying no, validating against reality, framing metrics and the business case, and writing the why down.
Every product call and its reasoning is logged in docs/PM_DECISIONS.md — positioning, scope-outs, math choices, surface decisions, and the post-ship corrections.
If you're hiring for APM or consumer PM roles, the live demo is the fastest way to see what I built and how it thinks. The repo is the fastest way to see how I built it.
— Ritika Das · ritikadas98@gmail.com






