ESP32-S3 Bench Guide
ESP32-S3 N16R8 bench guide: live pinout, lessons, simulators and the bench log.
GradeSmart AI bridges paper classrooms and digital grading. AI drafts rubric scores for typed papers and photographed handwritten tests, and nothing publishes until a teacher approves it.
EducationTypeScript · JavaScriptGitHub @NotMe22221OnlineOwner 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 ↗AI drafts the first pass. You make the call.
Markwise links paper classrooms to digital grading. Teachers grade typed essays and photographed handwritten tests with an AI first pass. They see where the class is losing points and can generate a parent-conference brief in one click. Students get a dashboard that plans the scores they need, tracks study streaks, and keeps every assignment in one inbox.
Every AI grade is a draft. Nothing reaches the gradebook or a student until a teacher approves it.
The interface follows Google Classroom's layout and vocabulary (class cards,
Stream / Classwork / People / Grades tabs, a To review grading tool, a student
To-do list), so teachers and students know their way around on day one. The
look is bright and bold: paint-blob shapes, patterns and heavy type around the
edges, Liquid Glass buttons, and plain surfaces wherever there is work to read.
Everyone can make it their own: light, dark, or follow the device; six accent
colors; a Bold or Calm look; four text sizes; a compact density; and Home pages whose sections and
class cards can be rearranged, moved between columns, or hidden. Search
(Ctrl/⌘ K) jumps to any class, assignment, or student. Icons are Google's
Material Symbols. See docs/DESIGN.md for the design system
and route map.
Every name, paper, photo, and number in this repo is synthetic demo data. Never point Markwise at real student data.
Requirements: Node.js 22.12 or newer (.nvmrc pins 22) and npm. No
database to install: the scripts start Prisma's built-in local Postgres server
for you. The very first start downloads Prisma's dev server from npm (a pinned
version, cached in node_modules/.cache), so it needs internet once; after
that, starting works offline.
npm install # installs dependencies and generates the Prisma client
npm run seed # starts the local Postgres, creates the tables, loads the demo data
npm run dev # http://localhost:3000
Open http://localhost:3000 and click View the demo to sign in as the demo teacher, Morgan Hale. See the student view signs in as the demo student, Ava Quillon.
No configuration is required: without API keys, every AI feature runs on a
labelled local demo engine. To use real AI, copy .env.example to
.env.local and set the keys. Next.js and the Prisma CLI both read
.env.local. Keys are read on the server only and are never sent to the browser.
| Variable | Default | Purpose |
|---|---|---|
ANTHROPIC_API_KEY | unset | Turns on Claude for every AI feature: the first pass on typed essays, the camera grader and answer-key reader (photos), and the conference briefs and goal planner. One key is enough. |
ANTHROPIC_MODEL | claude-haiku-5-5 | Claude model for every Claude feature. The default is the small, cheap model. Larger models can need a larger AI_DAILY_BUDGET_USD (see Different models). |
ANTHROPIC_EFFORT | medium | low / medium / high / xhigh / max, for essays and photos. Briefs and goals always run at low. |
OPENAI_API_KEY | unset | Optional. When set, OpenAI takes over the photo features (gpt-4o) and the briefs and goal planner (gpt-4o-mini). Essays stay on Claude. |
AI_KILL_SWITCH | off | 1 turns every live AI call off. Each feature then runs its labelled demo engine, as with no keys. |
AI_LIMIT_PER_SESSION_HOUR | 20 | Live AI calls one browser session may make in any hour. |
AI_LIMIT_PER_IP_HOUR | 60 | Live AI calls one network (client IP) may make in any hour, across sessions. |
AI_LIMIT_PER_DAY | 400 | Live AI calls the whole site may make per UTC day. |
AI_DAILY_BUDGET_USD | 2 | Estimated live AI spend the whole site may reach per UTC day. |
DATABASE_URL | local Postgres (started for you) | Postgres connection string. Set it to use your own database; hosts like Vercel + Neon set it for you. |
DATABASE_URL_UNPOOLED | same as DATABASE_URL | Direct (unpooled) connection used for schema changes (prisma db push). Neon on Vercel sets it. |
SESSION_SECRET | built-in demo secret |
Engine routing is decided in one place, engineFor() in
src/lib/ai/engines.ts. For photos and for briefs and goals it picks OpenAI
when OPENAI_API_KEY is set (gpt-4o for photos, gpt-4o-mini for text,
fixed in src/lib/ai/openai.ts), otherwise Claude when ANTHROPIC_API_KEY is
set (ANTHROPIC_MODEL), otherwise the labelled demo engine. Essays always use
Claude or the demo grader. The engines pill in the top bar shows the result.
Live AI limits. Every paid call (Claude or OpenAI) goes through one usage
guard (src/lib/ai/usage.ts). Before a call is sent, the guard records it in
the AiCall table at its worst-case cost. That cost comes from the request
size, max_tokens, and the price table in src/lib/ai/pricing.ts. The guard
then checks the caps above:
A call past a cap is never sent. The page says which limit was hit and when it
resets, and the route answers 429 with Retry-After. A call whose worst-case
cost alone is larger than the whole daily budget (AI_DAILY_BUDGET_USD) is
refused at once with 503 and no Retry-After, because waiting won't help. Its
message says to send fewer photos or less text, raise AI_DAILY_BUDGET_USD, or
choose a cheaper model. After a call, its row is settled at the provider's
reported token usage. The table lives in Postgres, so the caps hold across
every server instance. It stores keyed hashes of the session cookie and IP,
never the raw values.
Live AI reads are saved and reused. Claude essay first passes are keyed by (paper hash, rubric hash, model, prompt hash). The camera grader and the answer-key reader (Claude or GPT-4o) are keyed by the SHA-256 of every photo in order, the answer-key photo, the rubric, the model, and the prompt, schema, effort and output limit. Grading the same paper or the same photos against the same rubric again, or importing the same answer-key photo again, reuses the saved read: no new AI call, and nothing counts against the caps. A saved read goes through the same checks as a fresh one. Failed reads are never saved, and the demo engines are never cached.
npm run seed resets the demo data on the local dev server at any time.
Re-seeding signs everyone out. Seeded dates are relative to the day you seed,
so seed on demo day. It only ever erases the local dev server. When
DATABASE_URL points anywhere else (say, a hosted database after
vercel env pull), it names that database and stops without changing
anything. There is no override.
Dates follow the viewer's time zone ("Due today", Missing, Stream times), and a
due date is the same calendar day for everyone.
npm run eval grades a 30-essay public sample with the demo grader and
compares it with human scores. npm run eval -- --live plans the paid
Claude run: it prints the model, the call count and the worst-case cost, and
sends nothing until you confirm. See eval/README.md.
Other scripts: npm test (unit tests covering grading validation, prompt
injection, the demo engines, goal math, streaks, the heatmap, briefs, the
review queue, the class Stream and To-do, CSV export, and the seed dataset),
npm run typecheck, npm run build, npm start.
Demoing on a phone: run npm run dev -- -H 0.0.0.0 and open
http://<your-laptop-ip>:3000 on the phone. The webcam capture needs HTTPS or
localhost; on a phone over plain HTTP, Upload photos opens the camera instead.
Local database: Prisma's local Postgres server is named markwise.
npm run dev, npm run seed, and npm start use it straight away when it is
running and start it when it isn't (after a reboot, say); several of them can
start at once. npm run db:start starts it on its own, npm run db:stop stops
it, npm run db:status lists Prisma's local servers, and npm run db:delete
deletes it with its demo data. Its address is saved in
prisma/.local-database-url. To use another Postgres, set DATABASE_URL in
.env.local.
Troubleshooting:
npm run seed reports a schema change that would lose data: run
npm run db:delete (deletes the local demo database), then npm run seed.npm run db:status shows its state
(the same as npx prisma dev ls). Try npm run db:stop, then the command
again; if it still fails, npm run db:delete and npm run seed start over
with a fresh one (npx prisma dev rm --force markwise does the same
delete). Right after a crash, a start keeps trying for up to a minute
while the old server's lock clears.DATABASE_URL to any Postgres.Markwise needs a Postgres database when it runs on Vercel (serverless functions can't keep a local file). Neon's free tier works well.
gradeflow). Vercel detects Next.js and runs npm run vercel-build
(scripts/vercel-build.mjs).iad1 / US East by default), and connect
it to the project. This sets DATABASE_URL and DATABASE_URL_UNPOOLED.
Recommended: in the same dialog (or later in the Neon integration's
settings), turn on Neon's database branch for each Preview deployment,
so previews get their own copy of the data (see Preview deployments).SESSION_SECRET: a long random string (any 32+ characters).ANTHROPIC_API_KEY (runs every AI feature on Claude), and
OPENAI_API_KEY if you want OpenAI for photos, briefs and goals. Without
keys the labelled demo engines run. Keys stay on the server.prisma db push), loads the demo data only when the database is empty
(it never erases anything), then builds the app. Later deploys keep your
data. The build log shows each step and the database host (never the
connection string).Preview deployments. Preview builds don't change the database by default:
unless Neon gives each preview its own branch, previews share the production
database, and pushing a branch's schema would change production's (dropping
columns that branch removed). A preview build only compares its schema with
the database (read-only) and warns in the build log when they differ; that
preview then fails wherever it uses the difference. To let previews change
their own database, turn on Neon's preview branches (step 2), then add
ALLOW_PREVIEW_DB_CHANGES=1 in Settings → Environment Variables for the
Preview environment only. Each preview build then pushes the schema to its
own branch (which starts as a copy of production's data, so the demo seed is
skipped). Never set it while previews share the production database.
Schema changes. Production builds run prisma db push without
--accept-data-loss. Additive changes (a new table, an optional column, a
column with a default) deploy by themselves. A change that would delete data
(dropping or renaming a column that has values, a unique constraint over
duplicates) stops the build with an explanation and nothing is deployed. Split
it into additive steps, or apply it to the database yourself after checking
what it deletes. The schema changes before next build, and Instant Rollback
doesn't undo it, so keep each change compatible with the version before it.
Fresh demo data on the hosted site. The demo dates are relative to the day the data was seeded. Nothing in this repo erases a hosted or shared database. To start over:
DATABASE_URL and DATABASE_URL_UNPOOLED at it.The old database stays as it was until you remove it yourself.
Notes:
VERCEL, VERCEL_ENV). If a Vercel build log says "Build outside Vercel"
or "without VERCEL_ENV", turn on Settings → Environment Variables →
Automatically expose System Environment Variables.MARKWISE_DEMO_LOGIN=off once real accounts exist.maxDuration); Vercel's default Fluid
compute covers that on every plan.The click path for judges. Times are a guide.
The demo accounts are shared. On a hosted demo, everyone who clicks View the demo is the same Morgan Hale, so other visitors may already have graded a paper or logged a study session. The steps below say where that changes what you see. A fresh local seed matches them exactly.
A (or click Approve & return),
and confirm. The grade is returned to the student and lands in the gradebook.
(Shortcuts never fire while you type in a field.)More to show:
MARKWISE_DEMO_LOGIN=off they are
saved to the account. Then click Arrange on Home: drag a section by its handle (or use the move buttons, which
also work from the keyboard), move it to the side column, hide what you don't
need, and press Done. Class cards can be reordered the same way, and the
drawer follows.| Area | Status |
|---|---|
| Classes, rosters, assignments, rubrics, answer keys | Real. Stored in Postgres through Prisma. |
Essay submissions (pasted text or .txt upload) | Real. Stored verbatim as untrusted data. |
| Essay first pass | Real with ANTHROPIC_API_KEY (Claude, structured output against the rubric). Mocked without it: a deterministic demo grader, labelled on every screen. |
| Camera grader for handwritten tests | Real with ANTHROPIC_API_KEY (Claude) or OPENAI_API_KEY (gpt-4o): the model reads the photo, transcribes the answers, and scores each question against the answer key. Mocked without a key: the demo scanner recognises the bundled sample photos and uses their stored transcriptions. For other photos, you type the transcription. It scores by keyword overlap and is labelled as demo output. |
| Answer-key import from a photo | Real with ANTHROPIC_API_KEY (Claude) or OPENAI_API_KEY (gpt-4o). Mocked without a key: the sample answer key only. |
| Conference briefs | Real with ANTHROPIC_API_KEY (Claude) or OPENAI_API_KEY (gpt-4o-mini), from approved grades only. Mocked without a key: a deterministic template over the same data, labelled "Demo brief". |
| Goal planner | The model (Claude, or gpt-4o-mini with an OpenAI key) reads the goal (which class, what grade) and writes a short study tip with no numbers, or a question back when the goal names no grade. Both go straight to the student with no teacher review; the tip's engine badge says which model wrote it. A tip with a digit in it is replaced by a template tip. The scores needed are always calculated in code from approved grades. Without a key, a regex parser reads the goal, the tip comes from a template, and the planner is labelled "Demo planner". |
| Class heatmap, student profiles | Real. Computed from approved grades. No AI. |
| Study streaks and XP | Real. XP comes from the student's own actions (submitting, revising, logging study sessions, setting goals) plus a score-scaled bonus when the teacher approves and returns a grade. Only the student's own actions count toward a streak. XP is engagement only and never changes a grade. |
When a key is set, this is sent to Anthropic (or, for everything but essays, to OpenAI when its key is set):
/try, test photos and answer-key photos;Use synthetic data only.
Typed essay ───Claude / demo grader─────────────┐
├──▶ suggestion (AiGrade + per-criterion scores)
Test photo ───Claude or gpt-4o / demo scanner───┘ │ teacher edits, then approves
▼
PublishedGrade ──▶ gradebook, CSV, student view
src/lib/grading/, src/lib/scan/) only write suggestions and
move a paper into To review. They never write PublishedGrade.src/lib/grades/publish.ts is the only code that writes PublishedGrade.
Inside the same transaction it re-checks, from fresh database rows, that the
acting user is a teacher who owns the class, that the submission is in the
approved state with their approval on it, and that every score is within the
rubric's limits.PublishedGrade. Students never see
AI suggestions, answer keys, or unapproved scans.A only opens a confirmation dialog. It is ignored while
you type in a field, while a dialog is open, with modifier keys, and on key
repeat..env.local is gitignored).prisma/
schema.prisma data model (Postgres)
seed.ts, seed-data/ synthetic demo data
public/samples/ synthetic handwritten test and answer-key photos
src/
app/
page.tsx landing page (real screenshots in public/landing/)
try/ evidence/ public try-it sandbox; accuracy evidence from eval/results/
(teacher)/ Home, classes (Stream, Classwork, People, Grades), assignments,
To review, Paper Scanner, Gradebook, Insights, student profiles
(student)/student/ Home (streak, XP, class cards, inbox, goal planner), To-do,
Classes, class Stream / Classwork / People, assignments
api/ grade (camera), answer-key, conference-brief, goal-planner,
essay first pass, page photos, gradebook CSV export, search, try
components/
ui/ UI primitives (Liquid Glass buttons)
icons/ Material Symbols Rounded, generated by scripts/build-icons.mjs
app-shell/ top app bar, drawer / rail / bottom bar, account menu, engine status
customize/ Customize panel (theme, accent, look, text size, density, layout)
decor/ the Bold look's paint blobs, patterns and heading period
layout-editor/ arrange Home sections and class cards (dnd-kit + move buttons)
command-palette/ Ctrl/⌘ K search
classroom/ class banners, class avatars, class tab bar, underline tabs
review/ To review grading tool (the centerpiece)
scan/ insights/ briefs/ student/ assignments/ classes/ classwork/ dashboard/
gradebook/ landing/
lib/
ai/engines.ts which engine runs each feature (engineFor)
ai/claude-call.ts one structured-output Claude call (photos, briefs, goals)
ai/anthropic.ts the shared Claude client
ai/openai.ts OpenAI client and its fixed models
ai/usage.ts, usage-store.ts, guard.ts, pricing.ts
live AI caps (pure rules, AiCall table, request wiring), worst-case pricing
auth/ signed-cookie sessions, demo sign-in
access.ts ownership checks for every id-based read or write
grading/ essay graders (Claude + demo), validation, injection checks
scan/ camera grader (Claude or gpt-4o + demo scanner), answer-key reader, samples;
cache.ts (photo-read cache key and parsers, pure),
cache-store.ts (AiGradeCache store, server-only)
try/ try-it sandbox (cookie, run caps, grading)
eval/ ELLIPSE rubric and agreement metrics for npm run eval
grades/ review drafts, approve + publish (the gate)
insights/ briefs/ heatmap, student profiles, conference briefs
goals/ gamification/ goal math and parsing, XP, levels, streaks
review/ pure review-queue logic shared by client and server
classroom/ class Stream feed and due-date wording
ui/class-theme.ts class colors and banner art per class
preferences/ per-user look and layout (validated JSON, server actions;
a cookie per browser while demo sign-in is on)
search/ role-scoped search index for the command palette
export/ GradebookExporter interface + CSV exporter
eval/ 30-essay ELLIPSE sample and committed results (see eval/README.md)
scripts/ local-db.mjs (local Postgres), vercel-build.mjs (Vercel build),
build-icons.mjs (icons), capture-screenshots.mjs (landing images),
eval/run-eval.ts (npm run eval)
tests/ unit tests (node:test via tsx)
docs/ARCHITECTURE.md covers the data model, the
status machine, every API contract, and the extension points.
GradebookExporter (src/lib/export/types.ts)
for Canvas or Google Classroom and register it in src/lib/export/index.ts.
The CSV exporter is the reference implementation, and the gradebook shows an
Export <label> button for each registered exporter.Grader (src/lib/grading/types.ts), add
its kind, label, and tooltip description in src/lib/constants.ts
(GRADERS, GRADER_LABELS, GRADER_DESCRIPTIONS), and return it from
getGrader().ANTHROPIC_MODEL for Claude, or change
OPENAI_MODELS in src/lib/ai/openai.ts for OpenAI. Which provider runs
each feature is engineFor() in src/lib/ai/engines.ts. The usage caps
price each call from the table in src/lib/ai/pricing.ts, which lists
claude-haiku-5-5, claude-haiku-4-5, claude-sonnet-5-5,
claude-sonnet-5, claude-opus-5-5, claude-opus-5 and
claude-fable-5-1 at list price. Any other model is reserved at the top
tier ($10 / $50 per million tokens). Opus 5.5, Opus 5, Sonnet 5.5 and Fable
5.1 also reserve a server-side fallback rerun at the top tier. So with the
default $2 daily budget, a scan of 3 or more photos on claude-fable-5-1
(worst case $2.08 to $2.36) is refused as larger than the budget: raise
AI_DAILY_BUDGET_USD to scan with Fable 5.1. A 5-photo scan on
claude-opus-5 reserves about $1.77 and fits.src/lib/auth/ with your identity
provider and set MARKWISE_DEMO_LOGIN=off and SESSION_SECRET. Role and
ownership checks already run on the server.| Signs the session cookie. Set a long random string anywhere but a local demo. Required in production once demo sign-in is off. |
SESSION_COOKIE_SECURE | Secure except plain HTTP on localhost or a private LAN address | Force true or false. Behind a TLS proxy, make sure it sends X-Forwarded-Proto. |
MARKWISE_DEMO_LOGIN | on | Set to off to hide the one-click demo sign-in. |
| Output validation and flags | Real. Scores outside 0 to max are discarded. A quote must appear in the paper or transcription word for word, starting and ending on whole words, or it is dropped and the card says "No supporting quote found". A free local check flags a quote that may not fit its criterion as a "Weak match" (a heuristic, never a score change). Text addressed to the grader is flagged and never kept as evidence. |
| To review (the review queue), edits, approval, send-back | Real. Enforced on the server. |
| Worth rechecking (To review's default order) | Real. Worked out from stored data only, with no AI call: the validator's flags, the quotes it could or couldn't verify, the grader's self-reported confidence (a quiet note, never a reason on its own), and how often you changed the same grader's score on the same criterion in papers you already approved. Never a student's past grades, so a draft is never doubted for differing from a student's record. The weights are a rule of thumb, not a measured accuracy. Teachers only. |
| Gradebook and CSV export | Real. Reads only teacher-approved grades. |
| LMS integration (Canvas, Google Classroom) | Not built. CSV export is the stand-in; see Extending. |
| Authentication | Demo. One-click sign-in to seeded accounts (no passwords), plus a teacher-only "view as a student" switcher. Roles and ownership are still checked on the server for every page, action, and API route. |
| Students, papers, photos, classes | Synthetic. Fictional names, schools, sources, and statistics. The handwritten sample photos are rendered from a handwriting font. |
| Appearance and layout settings | Real. Validated on the server and rendered on the server, so pages load in the chosen theme. Saved per user in Postgres (UserPreferences). While one-click demo sign-in is on, they are kept per browser instead (a cookie for each demo account), because every visitor shares the demo accounts. |
| Search | Real. Built on the server from the signed-in user's own classes (teachers also see their students); no AI involved. |
| Landing page screenshots | Real screenshots of the demo app with synthetic data, captured by scripts/capture-screenshots.mjs. The hero's sample card is a drawn illustration, labelled Example, not grader output. No testimonials or usage numbers are claimed. |
| Dashboard stats | Real counts from approved grades. Time saved is an illustrative estimate (it assumes about 6 minutes per paper, is not a measurement, and is labelled that way). |
| AI confidence | The model's self-reported, uncalibrated confidence. Used only to help order the queue (a tie-breaker and a weak reason in Worth rechecking, and the Lowest confidence first sort), never shown as a probability. |
Try-it sandbox (/try) | Real. No sign-in: paste a paper, pick a built-in rubric, and see the first pass with highlighted quotes. It uses the same grader and validation as the app: Claude when a key is set (under the usage caps, with the calls left shown on screen), otherwise the labelled demo grader. Each sandbox can grade 30 new papers an hour, in demo mode too. Each browser gets its own sandbox: a random cookie, and rows in TryItRun keyed by its hash that are deleted after 24 hours. It never touches the demo classes and can't reach a student. |
Accuracy evidence (/evidence) | Real numbers on a small sample. 30 public essays (ELLIPSE corpus, CC BY-NC-SA 4.0) scored against human raters: exact agreement, within 1 point, mean error, and an always-3 baseline. The committed run is the demo grader, which does no better than the baseline. The live Claude run is a manual, paid step (30 calls to claude-haiku-5-5, at most about $0.13) and hasn't been run yet. See eval/README.md. |
| Live AI limits | Real. Per-session, per-network and daily caps plus a kill switch, enforced on the server from the AiCall table before any paid call is sent. Spend is an estimate from list prices, not a bill. |