Lexiverse
Lexiverse - AI contextual reading engine for English learners. Your next article is your next review.
Enter code to start interactive STEM drills
Translation pending
AI summary · website content
EducationJavaScript · PythonGitHub @ForzalabOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Render standard = the OpenUI Lang plan. Runbook: brain
projects/calc/_files/sprint-render-v2/SPRINT.md(plan:projects/calc/topics/openui-render-arch-plan-2026-10-18.md). No new view/widget format.
A code-gated STEM drill site (CALC1 / PHYS / CSCI26). It's a static page plus a small Python server, serve.py.
problems.json. Every problem, answer, wrong answer and hint. Format: SCHEMA.md (the only schema).
serve.py reads it, sends the browser only the public part of the problem whose code was typed (GET /p/<CODE>.json) and grades on POST /check. Edit or replace the file: live on the next request.problems.json and loads every problem in it.banks/BANK_XXX.json (same format). Type BANK_XXX in the code box: its questions become the list, and the server keeps each browser's progress and place (design/BANK.md). Ships empty.BANK_ + 3–6 capital letters or digits, e.g. BANK_CE3.json. Any other name is skipped (the server prints one line saying so). ce3-traps.json → rename to BANK_CE3.json.banks/ next to serve.py on the server (with deploy.sh: ~/stem-stuff-site/banks/). No restart: it's live on the next request.BANK_CE3 in the code box (bank ce3 works too), orhttp://<host>:<port>/#BANK_CE3 (e.g. http://csci4x.com:5567/#BANK_CE3).BANK_CE3. The server remembers each browser's progress and last question (cookie), so next visit reopens the same bank where they left off./banks/ is never served. Uploading a file instead (⬆ button) switches that browser to the file until they open a bank again.deploy.sh installs sympy if missing).tests/run-all.sh runs everything (unittest, npm test, every tests/*.pw.mjs with its server) and exits 1 on any failure; SHARD=1/2 / SHARD=2/2 (or --shard N/2) runs one half so the two can run at the same time (CI does), --list prints what a run would run; --quick = unittest + npm test (~10 s; as a pre-push hook: printf '#!/bin/sh\ntests/run-all.sh --quick\n' > .git/hooks/pre-push && chmod +x .git/hooks/pre-push). GitHub Actions runs the full set as two parallel shards (plus a required all check that needs both) on every PR (.github/workflows/tests.yml). First time: cd tests && npm ci../deploy.sh [dir] [port] [branch] (defaults ~/stem-stuff-site, 5567, main). Fresh → clone + serve. Existing install → shows incoming commits, asks "update the live site now? [Y/n]". Every run force-stops the old server (pid file, then anything still on the port) and starts a new one. Non-empty other dir → Exit (default) / wipe + install (type folder name to confirm).sw.js runs and the site works offline after one visit):
npm i -g vercel, vercel login, vercel link in the repo. In the Vercel project: Storage → add Upstash Redis (sets KV_REST_API_URL / KV_REST_API_TOKEN; tries live there, key stem:tries). Domains → add csci4x.com, set the DNS records it shows. Leave Git auto-deploy off.banks/: tools/ship.sh demo (prints a private preview URL) → check → tools/ship.sh live. It deploys origin/main plus your banks, never your working tree. Banks come from the brain first (newest commit wins; a stale copy in your checkout never beats it): a local brain clone, else the bank hatch (GitLab API, bank files only, BRAIN_TOKEN with read_api in .env; .env.example). --print-banks shows which, --no-brain skips; design/DEPLOY.md. A demo you liked → Vercel dashboard → Deployments → ⋯ → Promote (no rebuild).vercel.json builds public/ with tools/build_public.py (the client only) and sends /p/, /state/, /b/, /check to api/index.py, which bundles serve.py, problems.json and banks/. Answers never reach static files.STEM_REDIRECT=https://stem-stuff.vercel.app ./deploy.sh (same dir/port). It then sends every request there (302; POST /check 307), keeping the path and the #CODE. Run ./deploy.sh without it to bring the old site back.problems.json change is a redeploy (no hot reload). Reset a browser's tries: edit the stem:tries key in the Upstash console. The first check after idle is ~1–2 s slower (sympy loads).Every failure the app can catch gets one code, and the student sees it after the usual failure words: "Didn't load. Tap Try again. Send Tony this code: E-7K3QX". Never the reason (Tony, Oct 19).
Code: E- (made in the page, incident.mjs) or S- (made by serve.py, sent back in the X-Incident: S-XXXXX reason <trace id> header when Cluck failed before any text, so the page shows the server's code) + 5 characters from 23456789ABCDEFGHJKLMNPQRSTUVWXYZ (no 0/O/1/I).
Where to look: python3 tools/incident.py E-7K3QX prints the incident line(s), the page's snapshot as tables and every server line of the same trace, from Vercel's log search (VERCEL_KEY; runtime logs are kept a short time only), PostHog (POSTHOG_KEY, a personal phx_ key; months) or a saved log (--paste FILE, - = stdin). No KV or other paid store. By hand: Vercel → the project → Logs → search the code (one JSON line per incident; src = server or client). PostHog → event incident, filter code.
Line (OpenTelemetry names where one exists, no SDK): {"incident":"S-7K3QX","src":"server","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","where":"note","reason":"budget_cap","http.response.status_code":402,"gen_ai.request.model":"deepseek/deepseek-v4.1-flash","gen_ai.system":"openrouter","server.address":"openrouter.ai","exception.message":"HTTPError: HTTP Error 402: Payment Required","error.type":"HTTPError","q":"CALC1_XP1","t":"2026-10-19T17:02:11Z"}. The page's report (POST /log/incident) logs src: "client" with ua (browser family) and ua_raw (the full User-Agent). Every fetch sends a W3C traceparent; the same trace_id is on the page's incident and on every server line of that request, so an E- code and an S- code link.
Fields: where (explain, chat, mini, check, bank, problem, window, note, cluck, api, sw), reason (http, network, timeout, timeout_x2 = /check timed out twice in a row, parse, empty, upstream, budget_cap, exception, error, rejection, leak = a Cluck or mini-Cluck reply said a prompt's own word (the KEY, a prompt label: serve.leak_scan); err holds the pattern id only, never the text, sw_loop = a 3rd service-worker reload of one tab inside 60 s: it is skipped and the update bar shows instead), status / http.response.status_code, phase (headers = before the reply, stream = the reply broke, parse, timeout, script), (since the request started), (received before a break), / , / (the whole message, e-mails and long digit runs blanked), (question code), (deploy sha), , ; server only: , , . Never answers, notes or chat text. The PostHog event carries the page's names (, , , , ...).
An hourly GitHub Actions job (.github/workflows/spend.yml → tools/anthropic_spend.py) reads the Anthropic Admin API cost report and writes one KV key, stem:anthropic:spend = {"cents_month", "cents_since", "since", "at", "ok": true} (USD cents; month = current UTC calendar month). A failed run writes stem:anthropic:spend_err = {"ok": false, "err", "at"} instead and leaves the last good value alone (the err key is deleted on the next good run). The app estimates the balance as PREPAID_CENTS − cents_since.
The Admin API key must NEVER go into Vercel (or .env, or any file in the repo). It can read and manage the whole organization; it lives only in GitHub Actions secrets. The app only ever reads the KV key.
One-time setup (Tony):
sk-ant-admin01-…; org admins only).ANTHROPIC_ADMIN_KEY (step 1), KV_REST_API_URL and KV_REST_API_TOKEN (same values as the Vercel project's Upstash env vars).SPEND_SINCE = the date the prepaid credit was bought, YYYY-MM-DD (unset = 1st of this month).PREPAID_CENTS = credit bought on SPEND_SINCE, in cents (e.g. 5000 for $50). Not the Admin key.ANTHROPIC_ADMIN_KEY=… tools/anthropic_spend.py --dry-run.msbytespathurl.patherrexception.messageqvtrace_iduagen_ai.*server.addresshttp.request.methodincidentcodestatuspatherrbudget_cap: an AI provider said out of credit (OpenRouter 402 / key limit, Anthropic billing, our CLAUDE_BUDGET_USD cap). That request skips the provider at once; when every provider is out, /explain and /chat answer 503 with the code right away.
Debug mode (Tony only): open the site once with ?debug=1 (saved as stem-debug in this browser): codes show their reason too ("E-7K3QX network"). ?debug=0 turns it off. No switch on any page. Debug mode also shows the error card's diag line: v#197 · explain 504 · 20.1 s · AI: cap|err|ok|?.
Flight recorder (flight.js): the page keeps its last 256 steps in memory (nav, stage, pick, net start/headers/first byte/end/error with status, ms, bytes, rid = x-vercel-id + trace id, cluck, vis, conn, err, sw, layout: vp, scroll, cls, box, mq, load, ro; perf = long tasks folded into one entry per 30 s window: count, total ms, longest ms, JS heap MB on Chrome) plus 32 keyboard/focus events as classes (kd/ku/in/cs/ce/fi/fo/vv, and paste/cut/copy/submit as the event only, never the clipboard: target id, char or a named key, inputType, lengths, 100 ms times; never a value). An incident report carries fr = the last 64 steps, the last 20 keyboard events, page.at (the app's state: question, tries, Cluck, viewport, control boxes; no text) and page.before (the same, re-read in idle time 250 ms after each stage/nav/cluck/sw/vis step, with its age_ms: the page just before the error line), the diag line and /health's answer: ≤ 16 KB, ~5 KB typical; 3 per 10 minutes, the same failure within 60 s sends {dup, n} only. Look at it in the client line's fr (Vercel) or the PostHog incident event.
Server lines: every stderr line ends rid=<x-vercel-id> trace=<trace id>; a provider failure's incident line adds etype + emsg (the provider's own error type and message, 120 chars) and is kept as KV stem:err:<claude|openrouter> for an hour. GET /health answers {"claude","or": ok|over|err|null} only; the numbers (spend, budget, prepaid balance from stem:anthropic:spend with PREPAID_CENTS, last errors, or_key = OpenRouter's GET /auth/key limit / usage / limit_remaining in $) go in its health {...} log line. OpenRouter is asked at most once a minute per instance (kept in memory, a failure too; no KV), so /health (no rate limit) cannot hammer it; limit_remaining <= 0 or a 402 = over, 401/403 = err.
Trace line (one per /explain / /chat request, and per other API request that is not 2xx or takes > 2 s): {"trace":1,"trace_id","rid","path","status","ms","q","sid":sha256(sid)[:8],"v4","arm","fallback","fb_ms" (first byte),"bytes","done" (false = the client left mid-reply),"inc":[S- codes],"phases":[{"p":"note"|"or"|"inc","at" (ms since the request began),"model","status","ms" | "err"; inc: "code","reason","provider","etype","emsg"}],"t"}. Never the prompt, answers, notes or chat. Search the trace id or an S- code in Vercel → Logs (or tools/incident.py, which prints every line of a trace).
PostHog tile (paste it yourself: PostHog → Product analytics → New insight → SQL; nothing is created by code). Incidents by day, kind and reason, with their codes:
SELECT toDate(timestamp) AS day, properties.where AS kind, properties.reason AS reason, count() AS n,
uniq(properties.code) AS codes_n, arraySlice(groupUniqArray(properties.code), 1, 10) AS codes
FROM events
WHERE event = 'incident' AND timestamp > now() - INTERVAL 30 DAY
GROUP BY day, kind, reason
ORDER BY day DESC, n DESC
(DNT / GPC / opted-out devices send no incident event, so this undercounts them; server-only S- codes are in Vercel's logs only.)
Do Not Track / Global Privacy Control / privacy.html "Don't record me": no report is sent; the code still shows, with "Tracking is off on this device, so Tony gets less to go on."