Commit graph

53 commits

Author SHA1 Message Date
spearzificial
5a562176ae Diagram pipeline: import 1,856 image MCQs with local figures, sanitized img allowlist, WebP shrink 2026-10-17 13:09:21 +05:30
spearzificial
68a287ac78 Dashboard weakest-subtopics table gains % column for result-page consistency 2026-10-17 11:28:06 +05:30
spearzificial
c58bb0c5da Time analysis on result page + per-question stopwatch in exam (fixes time_secs never recorded) 2026-10-17 11:21:34 +05:30
spearzificial
deaa6d9b3d Result page: share box with copy-link button for the paper's public URL 2026-10-17 11:03:35 +05:30
spearzificial
22c6c24e87 Result page: score-banded encouragement card (ZenithPrep modal analogue) 2026-10-17 10:53:36 +05:30
spearzificial
adefc976fe Result page: sticky left nav with section anchors, negative-marks card, mistakes badge 2026-10-17 10:45:30 +05:30
spearzificial
b12f7dcac1 Bust Cloudflare cache for style.css and exam.js via ?v=4 2026-10-17 10:22:17 +05:30
spearzificial
aae54300be Replicate ZenithPrep active-exam UI: white question card with inline options, 5-state CBT palette, NTA action bar 2026-10-17 10:04:23 +05:30
dev
a0fcfc93b6 Title-case subtopic names via scripts/titlecase.py
300 of 483 subtopics normalized ('Addition And Subtraction Formula'
-> 'Addition and Subtraction Formula'). Acronym FIX map preserves
AC/AM/GM/HM/AP/GP/EM/EMF/RMS/SHM/GOC/IUPAC/pH; article-A and PN
Junction fixed; VSEPR and Raoult's Law proper nouns corrected.
Chapter names were already title case. Applied to live tablet DB.
2026-10-16 15:20:14 +05:30
dev
374e2e2056 Paper preview in ZenithPrep style: stat cards, subject sections
shared() now renders: stat cards (questions, duration, max marks,
+4/-1), per-subject sections with q-ranges and chapter lists, a
'take this paper' card with the save-and-next explainer and privacy
note, then the full question list. New .kv/.grid2/.subj-sec/.ch-list
CSS with mobile single-column fallback.
2026-10-16 14:56:27 +05:30
dev
600ff95315 Fix rich() eating raw < in math, title-case chapter titles
Sanitizer root cause: a '<' followed by space/digit (e.g. '\(a < 0\)')
was parsed as a tag, failed allowlist, and got DELETED along with its
content — unbalancing KaTeX delimiters and killing all math after it
on the page. Now only '<' followed by letter, /, ! or ? starts a tag;
everything else becomes &lt;. 4 regression tests added.

CSS: .zp-chk-wrap and .zp-subtopic-row labels no longer inherit the
generic uppercase rule, so chapter names render in DB title case
('Quadratic Equations', not 'QUADRATIC EQUATIONS').
2026-10-16 14:38:23 +05:30
dev
5b732080d6 Bump favicon and apple-touch-icon to v3 (darkened artwork) 2026-10-16 13:23:49 +05:30
dev
21dfdde92d Cache-bust logo and embed URLs (v3) for darkened artwork 2026-10-16 13:16:13 +05:30
dev
71b637c9fb Darken whale logo to match site theme, username placeholder
- Logo: modulate 78 brightness / 88 saturation on the v3 whale source.
  Verified clean (no line artifacts on black-flatten). Regenerated
  logo.png, favicon.png, apple-touch-icon.png, and embed.png from it.
- Username input placeholder: 'e.g. Aarav Sharma' -> 'e.g. Nikhil The Great'.
2026-10-16 13:08:16 +05:30
dev
28e4c09e06 Cache-bust favicon and apple-touch-icon URLs (v2)
Cloudflare edge cached the old placeholder bytes on the bare
/static/favicon.png and /static/apple-touch-icon.png URLs. The ?v=2
query string forces a fresh origin fetch with zero behavior change.
2026-10-16 12:17:14 +05:30
dev
84060de856 Replace placeholder logo with user's whale artwork
- logo.png: user's whale (1024px JPEG) with background flood-filled to
  transparent at fuzz 3 (preserves white belly details), alpha
  thresholded to kill JPEG-noise halos, trimmed and centered on
  720px canvas. v3 verified clean: no streaks, no box lines, no fringe.
- favicon.png (32px) and apple-touch-icon.png (180px) regenerated
  from the same source.
- embed.png (1200x630) rebuilt: whale at 440px left, no overlap with
  the WhalePrep title text.
- views.rs: nav brand now references logo.png instead of logo.svg.
2026-10-16 12:04:45 +05:30
dev
b1b6887a26 Add whale logo, favicon, apple-touch-icon, and social embeds
Logo: hand-crafted SVG whale silhouette in blue gradient (#3b82f6 to
#1d4ed8), DeepSeek-inspired minimalist style with water spout, eye,
and wave arc. Renders at 28px in the nav header.

Embeds: Open Graph and Twitter Card meta tags on every page. og:image
is a 1200x630 embed card with the whale logo, WhalePrep title, tagline,
and stats. Favicon.png (32x32) and apple-touch-icon.png (180x180)
generated from the same SVG. theme-color #0a0f1c for mobile browsers.
2026-10-16 00:53:46 +05:30
dev
51a01cc2cf Degree-symbol fix (4 questions), document database search findings
º (masculine ordinal U+00BA) -> ° (degree U+00B0) in Q35, Q963, Q5359,
Q9014. DATA.md now records the better-database audit: the
Indian_Exams_PYQ repo's 223 shift PDFs are LFS-locked and image-only;
examgoal CDN returns 403; our examgoal-derived bank stays the best
structured source. Also explains the Hard-difficulty reality (user-vote
metadata, ~2-3 hard questions per shift is authentic) and confirms all
9,813 questions carry exam='jee-main' source metadata.
2026-10-15 10:33:18 +05:30
dev
02ab131c72 Reorganize bank to official NTA JEE Main syllabus units
The chapter structure now matches the NTA 2026 Paper 1 syllabus exactly
(verified against the official PDF at cdnbbsr.s3waas.gov.in):
- Physics: 20 units (Units and Measurements, Kinematics, Laws of Motion,
  ... Electronic Devices, Experimental Skills)
- Chemistry: 21 units (Some Basic Concepts, Atomic Structure, ...
  Principles Related to Practical Chemistry)
- Maths: 14 units (Sets Relations and Functions, Complex Numbers, ...
  Trigonometry)

scripts/reorg_nta_units.py maps upstream subtopics by keyword; 73
stragglers placed manually. Removed the 24 hand-written demo questions
(Q1-Q24) from the live bank — trivial one-liners that diluted the
difficulty pool. Bank is now 9,813 pure PYQs (2002-2025, all
exam='jee-main' verified from source metadata).
2026-10-14 19:17:47 +05:30
dev
7c4b1a4009 Fix stray dollars, remove dead UI, clear selection feedback, count input
Dollar fix: 5,634 questions had a lone $ before \( or $$ from the
source data. fix_single_dollar now strips any orphan single $ while
preserving $$..$$ display pairs; the live tablet DB was patched in
place and the importer inherits the fix.

UI: removed the non-functional 'previous year paper' mode card, the
complete-syllabus button, the tick glyph inside filter pills, and the
'2025 Syllabus' badge beside the header title. Question count pills
replaced by a plain number input (5-75). Checked chapters now show a
blue left-edge highlight so a second click visibly deselects; checked
subtopics tint blue the same way.
2026-10-14 16:30:17 +05:30
dev
ab7486bcc2 ZenithPrep-style build page with difficulty and year filters
Replicates zenithprepare.com/PYQ topic-selection UX:
- Subject switcher pills (Maths/Physics/Chemistry) with live selection
  counters; only the active subject's chapters are shown.
- Searchable chapter rows with independent subtopic expand arrows
  (checking a chapter no longer toggles the tray).
- Complete-syllabus one-click button (selects all 15 units, 75 Qs,
  JEE mode).
- Difficulty filter pills (Easy/Medium/Hard, default all) and year
  filter pills (2018-2025, default 2020-2025) wired through new
  difficulties/years form fields; build_paper filters both, including
  the FIFO recycle path. Verified end-to-end: Hard-only paper returns
  only Hard questions; Medium+Hard across 2022-2025 leaks zero Easy.
- Settings bar with count pills, JEE-mode toggle and live pool
  estimate.
Also fixes: brand header badge, raw-string delimiter clash with SVG
hex fill, Maths subject-key mismatch between pills and DB.
2026-10-14 10:14:46 +05:30
dev
54ba844f92 Revamp UI/UX, fix UTF-8 mojibake, normalize question typography, and reorganize JEE portions
1. UTF-8 Bugfix: Fixed byte-casting in views::rich() that turned multi-byte UTF-8
   sequences into Latin-1 mojibake (e.g. 2.06 × 10 -> 2.06 × 10).
2. Question Typography: Replaced nested <p class='stem'> with <div class='stem'>
   and strict CSS overrides ensuring consistent 1.0625rem font-size and weight
   across all 9,837 questions regardless of inner tags.
3. Modern Educational Theme: Removed harsh minimal 0-radius monospace styling;
   introduced comfortable slate dark theme (#090d16), rounded corners (8-12px),
   Inter typography, and refined option card states.
4. Topic Selection (/build) Revamp:
   - Added Quick Presets bar (Full Mock 75 Qs, Physics/Chemistry/Maths focus, Clear).
   - Added Subject Navigation Tabs (All, Physics, Chemistry, Mathematics).
   - Independent subtopic expand chevrons that don't force-check parent chapters.
   - Live question pool estimator and interactive count pills.
5. JEE Portion Reorganization (scripts/reorganize_jee_db.py): Re-categorized
   the database into the 15 official JEE Main units across Physics, Chemistry,
   and Mathematics.
2026-10-13 11:09:12 +05:30
dev
d312eaff3a Rust-native optimizations: transactional writes, O(1) history, SQLite PRAGMAs, and native bench tool
1. Batch write transactions in build_paper, start_attempt, save_answers,
   and finalize (turned 75+ disk operations into 1 commit per action).
2. SQLite PRAGMAs: synchronous=NORMAL, cache_size=64MB, temp_store=MEMORY,
   and mmap_size=256MB for zero-copy memory reads.
3. O(1) history: record right_count/wrong_count on attempts at finalize time;
   history() loads in a single SELECT query without re-grading loops.
4. Native Rust load testing tool (src/bin/bench.rs) replacing Python scripts:
   multi-threaded concurrent reads & simulated full student lifecycles with
   sub-microsecond timing and statistical percentiles.
2026-10-13 10:05:27 +05:30
dev
8a89f6045b Fix bench_write.py: indentation, state API instead of local DB
The UA edit broke indentation (script never ran since). Question ids
now come from /api/attempt/:id/state so the benchmark works against
any deployment. Verified live: 10 students, 45 writes/s through tunnel.
2026-10-13 09:18:20 +05:30
dev
ba10e70830 Backup script resolves symlinks, aborts without DB
Invoking via ./backup.sh symlink made dirname escape the repo and sqlite
created an empty DB in the parent dir. Now uses readlink -f with a
location check plus a guard that refuses to run without ./whaleprep.db.
2026-10-13 09:06:45 +05:30
dev
288ccef090 Backup script removes same-day snapshot before VACUUM INTO
VACUUM INTO refuses to overwrite, so re-runs silently kept stale data
(backup showed 6 users, live had 2). Caught by comparing counts.
2026-10-13 09:05:17 +05:30
dev
6efbb61afc Indexes on papers.share_token and mistake_tags.attempt_id
Both are per-request lookups (shared-paper view, result tags, dashboard
rollup) that full-scanned. Zero warnings, 25 tests green.
2026-10-13 08:49:37 +05:30
dev
338aa2e8ef Print stylesheet: light surfaces for options/inputs, print buttons
Exam/solution pages print clean via browser print-to-PDF: interactive
chrome hidden, questions unhidden, ink-friendly colors. Options printed
as black bars without the surface override — .opt/.chk/inputs/palette
now forced white. Verified with print-media emulation screenshot.
2026-10-13 08:28:54 +05:30
dev
32f5e7a440 Remove dead SubtopicRow struct (zero-warning build)
Unused since the chapter-block refactor. Build is warning-free;
25 tests green (16 unit + 2 form + 7 grading).
2026-10-13 08:17:54 +05:30
dev
3a785099bc Coach notes use mistake tags and guessing history
coach_notes takes the tag rollup the dashboard already fetches: a
dominant mistake type (>=half of >=2 tags) yields a targeted fix, and
chronic wrong-over-blank across history yields a guessing warning. Same
Vec<String> output, no view changes. Three unit tests cover fire/quiet
paths.
2026-10-13 08:11:10 +05:30
dev
39332670be Enforce 30-day session expiry with lazy cleanup
Sessions never expired server-side despite the 30-day cookie Max-Age;
repeat sign-ins orphaned rows. user_id now rejects tokens older than 30
days and deletes them on sight. Verified live: fresh 200, backdated 302
with row removed.
2026-10-13 00:20:58 +05:30
dev
0f2a9743f2 SECURITY.md: audit result, rsa advisory assessment, session/data notes
cargo audit finds one advisory (RUSTSEC-2023-0071, no fix). Assessed
not exploitable here: server only verifies RSA signatures, holds no
private key. Session, Google verification, rate limit, backup posture
documented alongside.
2026-10-13 00:12:08 +05:30
dev
ad8e878af8 Daily SQLite backup script (VACUUM INTO, 7-day rotation)
Live user data had only a stale pre-migration backup. First snapshot
verified: 9,837 questions, 6 users. Termux has no cron; script is manual
or Tasker-driven.
2026-10-13 00:06:20 +05:30
dev
cc0cd75ff9 Document NUM exclusion rationale (audited, not assumed)
Integer explanations end with the answer but phrasing varies enough
that regex extraction would mis-key questions. A wrong key is worse
than no question. NUM stays hand-added until NTA keys transcribed.
2026-10-13 00:02:53 +05:30
dev
b9763c44cc Add multi-stage Dockerfile (17MB binary, SQLite volume, healthcheck)
rust:1.99-slim build, debian-slim runtime as uid 10001. Only static/
and content/ shipped alongside the binary; DB lives on /data volume.
Could not build-verify locally (no Docker daemon); paths checked
against ServeDir/include_str usage instead.
2026-10-12 23:58:34 +05:30
dev
86c883fb91 Per-user autosave rate limit (30/min, 429 past it)
The exam screen autosaves on every answer; a stuck client retrying in a
tight loop could hammer SQLite's single writer. Ctx carries a small
in-memory per-user windowed counter with opportunistic cleanup — no
Redis needed. Verified live: 30x200 then 429s.
2026-10-12 23:50:49 +05:30
dev
dc754fe996 Student sign-in UX, gzip, landing stats cache
Sign-in page rewritten for non-technical students: Google-first when
configured, plain-language email path with honest 30-day scope, recovery
note, placeholders, full-width CTA. deploy/termux-start.sh sources .env
for GOOGLE_* credentials; deploy/env.example documents the 5-minute
Cloud Console setup; .gitignore covers secrets, DBs, pid/log files.
bench scripts send a browser UA so Cloudflare bot checks stop 403ing
local load tests. Landing bank counts cached 60s (five aggregate
subselects no longer run per hit). gzip compression on all responses.
2026-10-12 23:33:33 +05:30
dev
5f4539342a Fix math rendering: inline delimiters, matrix rewrite, TeX-ism cleanup
Source data wraps every math fragment in display-mode $$ delimiters,
which KaTeX rendered as centered blocks mid-sentence. scripts/fix_math.py
normalizes the corpus: $$/$ pairs become inline \\(...\\) (block
derivations with newlines or environments stay display), legacy
\\matrix blocks become begin/end matrix, _\\limits/^\\limits,
\\raise/\\lower boxes, \\root/\\of, \\tag and \\, are rewritten
to KaTeX-supported forms. Batch-validated 60k inline spans: 99.98% render
clean; remaining handful are source pathologies that degrade to text.
import_examgoal.py runs the same normalization at write time.
2026-10-12 23:25:45 +05:30
dev
c4080976bc Deploy artifacts: Termux service scripts plus tunnel config
deploy/termux-{start,stop,status}.sh manage the release binary on the
tablet; deploy/cloudflared-whaleprep.yml is the dedicated tunnel config
for whaleprep.oiupoyt.space. README gains a deploy section.
2026-10-12 22:26:33 +05:30
dev
ff1c23eaec Vendor examgoal unpickle models, portable pickle path
import_examgoal.py required /tmp/pyqeval helper modules that were never
committed, so it failed on any fresh machine. The two upstream model
files now live in scripts/examgoal_model/ (relative import fixed) and
the default pickle path is scripts/examgoal_db.pkl.
2026-10-12 20:34:50 +05:30
dev
a7e45a695d README: WhalePrep setup, import pipeline, measured specs 2026-10-12 15:27:44 +05:30
dev
f796c566b9 Owner paper route plus take-paper CTA on shared pages
GET /paper/:id redirects the owning user to their attempt screen (404
otherwise, login wall otherwise). Shared /s/:token pages now end in a
real action instead of a dead list: signed visitors get a take-this-paper
button, anonymous visitors get a sign-in link preserving the share URL
as next. Verified live: 1/302/1 across the three states.
2026-10-12 13:25:17 +05:30
dev
231dd0385e FIFO question recycle: exhausted subtopics return oldest-seen first
seen_questions gains seen_at (with ALTER TABLE migration for existing
DBs, index created after the migrate step). Submit stamps seen_at with
an upsert so repeats refresh recency. build_paper tops up short
unseen-only pools from longest-ago-seen in scope instead of erroring.
Verified live: 1-question subtopic served the same seen question on
repeat builds (303, not 400).
2026-10-12 13:01:35 +05:30
dev
4427837e7a Mistake tagging: per-question why-lost form plus dashboard rollup
Result page shows a why-lost row (concept gap / calculation slip /
misread / out of time / blind guess) on every wrong or blank question.
POST /result/:attempt/tag/:qid toggles one tag; ownership checked,
vocabulary enforced server-side. Dashboard gains a 'why you lose marks'
section aggregating tags across all submitted attempts. Verified live:
tag persists, rollup renders.
2026-10-12 12:44:30 +05:30
dev
ad9eba1ca3 Fix N+1 chapter counts, add indexes, drop dead query branch
load_chapter_blocks used a correlated COUNT subquery per chapter row:
~5s on the 9.8k-question bank. Rewritten as one GROUP BY joined once
(16ms p50 on /build). Nine CREATE INDEX statements added to init_db
so fresh and existing DBs both get them. build_paper had a dead
placeholder branch issuing a subtopic_id=0 lookup per chapter; removed.
2026-10-12 12:12:29 +05:30
dev
45d2f38b0c Import 9,813 real JEE Main PYQs, KaTeX math, DB indexes
scripts/import_examgoal.py pulls the MIT-licensed examgoal-derived
14,973-question pickle and keeps the 9,813 fully-text MCQs with single
correct answers (2002-2025, all subjects). chapterGroup becomes our
chapter, chapter slug becomes our subtopic. Idempotent: skips stems
already present.

views.rs gains rich() — a read-side sanitizer matching the importer's
allowlist — and all six stem/option/solution render sites use it.
KaTeX auto-render (trust:false) added to head() for $$..$$ math.

db.rs gains nine CREATE INDEX statements; the chapter loader query
drops from ~5s to 6ms on the 9.8k-question bank. DATA.md rewritten
around the real pipeline with attribution.
2026-10-12 10:55:20 +05:30
dev
5c4b8ab7f2 Rename zenith-pyq-clone to whaleprep
Package, binary, tracing filter, footer copy, run_server.sh and README
updated. Footer no longer attributes zenithprepare.com.
2026-10-12 08:17:40 +05:30
dev
3df1d62002 Add expand_seed and document data sourcing
scripts/expand_seed.py generates synthetic questions across the full JEE Main
syllabus taxonomy (15 chapters, 45 subtopics, correct MCQ/NUM split). Stems
are deliberately sparse so no-one mistakes them for NTA content.

DATA.md documents why there is no real scraper: NTA PDFs render questions as
images; pdftotext extracts only IDs. Every aggregator with structured data
built it through manual entry pipelines. The document lists realistic paths
to a real bank and the SQL schema for loading one.
2026-10-12 05:19:03 +05:30
dev
1d8ee3b5ff Subtopic picker, JEE mode, per-subject timers
The builder previously exposed only chapters. Now each chapter expands to show
its subtopics as individual checkboxes: selecting a chapter checks all of its
subtopics, selecting the subject-level box checks everything, and selecting
individual subtopics sets the chapter to indeterminate. The exam query filters
by subtopic_id when any are given; the SQL path deduplicates so a question
matching two subtopics only appears once.

JEE mode sets duration to 60 × unique-subject-count (180 min for a full paper).
On the exam screen this renders per-subject countdown strips: Physics 60:00,
Maths 60:00, Chemistry 60:00. Each strip counts down independently; when one
hits zero the screen auto-jumps to the first unanswered question of the next
subject. The last strip exhausting time submits the form.

Single-subject papers keep the flat timer and show no strips.

Also: all audit targets now genuinely 44px (the .opt labels were 42px).
2026-10-12 05:13:09 +05:30
dev
14542f169a Make the exam screen work on a phone
The screen had no mobile state at all: the clock scrolled away mid-paper, the
only navigation was save-and-next so a candidate could not go back, the palette
sat below the question where it pushed submit off-screen, and the nav row lost
three of its four links under 700px.

Sticky header carries the clock and a progress bar; previous/next are real
buttons; the palette is a drawer below 1024px opened by a floating button that
sits above the tab bar. Primary destinations move to a bottom tab bar on
phones, data tables scroll inside their own card, and every control meets the
44px touch minimum.

Verified with a headless browser across five viewports rather than by reading
the stylesheet: scripts/audit_mobile.py fails on horizontal overflow, tap
targets under 44px, and content hidden behind a fixed bar; scripts/shots.py
captures each page at both widths. Both caught bugs I would otherwise have
shipped, including a palette toggle that lived inside the drawer it opened and
so could never be pressed on a phone.

fmt_dur drops a leading zero hour, so a 24 minute paper reads 24m 00s.
2026-10-11 21:54:17 +05:30