DEV Community

FLUX
FLUX

Posted on

Code review

Popup (Upsell) Feature — Self-Review Plan

Fastest review path — bottom-up. Each step makes the next one self-explanatory.

Total time budget: ~30 min for thorough review, ~12 min for the critical core only.


Sequence (follow top to bottom)

# Phase Time Files Why this order
1 Data contract 2 min shellel.server/src/usage-tracker/usage-tracker.schema.ts Tiny. Tells you the only 5 fields that matter: key, anonId, ip, count, lastUsedAt. Everything below is just reads/writes against this.
2 Server source of truth 5 min shellel.server/src/usage-tracker/usage-tracker.service.tsusage-tracker.module.ts reserve() is the entire backend logic. Understand the atomic $inc, the anon: vs ip: key precedence, and the fail-open. Module file is one glance.
3 Server enforcement 5 min shellel.server/src/calorie-check/calorie-check.controller.ts (L43-58) → shellel.server/src/protein-plan/protein-plan.controller.ts (L96-111) Both controllers do the same 3 lines: reserve() → if !allowed throw 403. Read calorie first, diff protein in your head. While here, answer: is /protein-plan/calculate skipping the reserve on purpose?
4 Client state mirror 5 min shellel-client/src/app/shared/usage/usage-tracker.service.ts This is the client twin of step 2. Focus on count + showUpsell signals, getAnonId/setAnonId, and isOverLimit. Note it diverges from server (localStorage vs Mongo) — keep that in mind for step 5.
5 Client integration — pick ONE feature, fully 7 min shellel-client/src/app/calorie-checker/calorie-checker.ts (L40-41, 171-173, 282-308, 326-329) + calorie-checker.html (L61, 275) Trace the 4 trigger points in one feature: mount-time check, pre-call gate, 403 catch, post-success increment. Once this clicks, you understand the whole pattern.
6 Diff the second feature against the first 5 min shellel-client/src/app/protein-planner/protein-planner.ts (L228-238, 249-251, 393-431, 454-457) + protein-planner.html (L183, 448) Don't re-read — only look at what's DIFFERENT from calorie. The only real diffs: setAnonId from saved profile (L228-229, 250), and an extra "set up profile first" guard. Asymmetry confirmed → flag it.
7 Popup UI 3 min shellel-client/src/app/shared/upsell/upsell-modal.component.ts.html.css Saved for last on purpose — it's pure presentation. Only logic is the iOS UA detection. After steps 1-6 you'll read this in 30 seconds.
8 Edge-case sweep 5-8 min (no new files) Walk down the 9-item issue list with the code already in your head. Each takes <1 min because you already know where to look.

Why this order saves time

  • Data → server → client → UI is the natural dependency direction; you never read code that depends on something you haven't seen.
  • Step 5 → 6 deliberately exploits the parallelism. Calorie and Protein are ~95% identical (the graph report flagged this with semantically_similar_to edges). Reading both fully is wasted work — read one, diff the other.
  • Popup UI last because it has no logic worth fighting over until you know what state it's reflecting.

Time budget summary

Scope Time What you cover
Minimum viable review ~12 min Steps 2, 3, 5 — the spine of the feature
Standard review ~20 min Steps 1-6 — full understanding, both features
Thorough review ~30 min All 8 steps including edge-case sweep

One concrete starting move

Open these 3 in split panes side-by-side:

  • shellel.server/src/usage-tracker/usage-tracker.service.ts
  • shellel-client/src/app/shared/usage/usage-tracker.service.ts
  • shellel-client/src/app/calorie-checker/calorie-checker.ts

That triple is the spine of the feature. Everything else is wiring or chrome.


Edge cases to verify during Step 8

  1. POST /protein-plan/calculate does NOT call usageTracker.reserve() (only /check does). Intentional (onboarding is free) or a gap?
  2. Client count and server count drift independently. localStorage vs Mongo — after clearing localStorage user gets fresh client UI but server may already be over limit (caught via 403 path).
  3. Server fail-open on Mongo error (usage-tracker.service.ts:53-56) — Mongo outage = unlimited free checks. Document if intentional.
  4. Key precedence is anonId OR ip, never both. Clearing localStorage = fresh counter. Acceptable for soft paywall.
  5. x-forwarded-for is trusted raw. Fine behind a proxy; spoofable if directly exposed.
  6. Pre-check gate uses client-only isOverLimit(). Server is source of truth — good defense in depth via 403 branch.
  7. Asymmetry: Calorie counter is per-device; Protein unifies across sessions via setAnonId from saved profile. By design?
  8. No backdrop ESC-key close — only click outside + X button (a11y nit).
  9. maxLength=36 on anonId DTOs but no UUID format validation server-side (only on /history/:anonId). Arbitrary 36-char strings can be posted as counter keys.

Top comments (0)