PaperLoom is a secure physical-to-cloud learning workspace. It connects a scanned page or simulated hardware scan to a cryptographic document fingerprint, stores the page in Supabase behind Row-Level Security, runs a grounded curriculum audit, and opens a student dashboard tailored for focused review.
The application is built for learning workflows where provenance matters: each dashboard is tied to a SHA-256 page identity and accessed with a short-lived fingerprint-bound JWT.
- Browser-based scan console with editable extracted text and local SHA-256 fingerprint generation.
- ESP32 Wokwi scanner simulation with OLED status UI, push-button scan trigger, and cloud upload.
- Server-side ingest pipeline with payload validation, Supabase persistence, Tavily grounding, Gemini curriculum auditing, and JWT issuance.
- Supabase PostgreSQL schema with strict fingerprint-based RLS for
books_and_notesandeducational_gaps. - Secure student dashboard that fetches directly from Supabase using the custom JWT and RLS policies.
- Adaptive Cognitive Workspace modes:
- Standard: full dashboard with overview, page scan, learning gaps, sources, and practice.
- Bionic: widened scanned-text reading layout with guided visual emphasis.
- Horizon: single-card focus mode for one critical learning node at a time.
- Optional Render automation for creating per-student static workspace services.
- Next.js App Router
- React 19
- TypeScript
- Tailwind CSS
- Supabase PostgreSQL and Row-Level Security
- Gemini API for structured curriculum analysis
- Tavily Search API for trusted educational grounding
- Render API for optional workspace deployment
- Wokwi ESP32 simulator with SSD1306 OLED
app/
api/
deploy/route.ts Protected manual Render deployment endpoint
ingest/route.ts Scan ingest, audit, persistence, JWT minting
dashboard/[hash]/page.tsx Secure student workspace dashboard
globals.css Global Tailwind and PaperLoom UI tokens
layout.tsx Root metadata and app shell
page.tsx Browser scan console
lib/
renderDeployer.ts Render service creation utility
supabase/
migrations/ Database schema, grants, functions, and RLS policies
wokwi/
sketch.ino ESP32 scanner simulation
diagram.json ESP32, OLED, and button wiring
libraries.txt Wokwi library dependencies
Browser scan console or ESP32 simulator
|
| POST /api/ingest
v
Next.js ingest route
|
| validate payload, upsert page, run Tavily + Gemini audit
v
Supabase books_and_notes + educational_gaps
|
| issue short-lived JWT with fingerprint_hash claim
v
Student dashboard /dashboard/[hash]
|
| client-side Supabase fetch with custom JWT
v
RLS-authorized page content, gaps, sources, and practice UI
- Node.js
20.11.0or newer - npm
- A Supabase project
- Gemini API key
- Tavily API key
- Render account and API key, only if using workspace deployment automation
npm installCreate .env.local in the project root.
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
SUPABASE_JWT_SECRET=
GEMINI_API_KEY=
TAVILY_API_KEY=
# Optional
SUPABASE_URL=
GEMINI_MODEL=gemini-2.5-flash
NEXT_OUTPUT_MODE=
# Optional Render automation
RENDER_AUTODEPLOY_WORKSPACES=false
RENDER_API_KEY=
RENDER_OWNER_ID=
RENDER_DASHBOARD_REPO=
RENDER_DASHBOARD_BRANCH=main
RENDER_DASHBOARD_ROOT_DIR=
RENDER_ENVIRONMENT_ID=
RENDER_DASHBOARD_BUILD_COMMAND=npm install && npm run build
RENDER_DASHBOARD_PUBLISH_PATH=out
RENDER_API_BASE_URL=https://api.render.com/v1Keep server-only secrets out of client code. Only NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY are intended for browser exposure.
Run the SQL files in order against your Supabase database:
supabase/migrations/202608010001_paperloom_core.sql
supabase/migrations/202608010002_service_role_ingestion_grants.sql
These migrations create the core tables, helper functions, grants, triggers, indexes, and RLS policies.
npm run devOpen http://localhost:3000.
- Open the PaperLoom scan console.
- Review or edit the extracted text.
- Click
Connect page. - The browser generates a SHA-256 fingerprint and calls
/api/ingest. - The ingest route stores the scan, runs the educational audit, and returns a short-lived JWT.
- The client stores the token in
sessionStorage. - Open the generated
/dashboard/[hash]workspace. - Use Standard, Bionic, or Horizon mode depending on the student’s learning need.
Accepts a scan from the browser console or ESP32 simulator.
Request body:
{
"device_id": "paperloom-browser-console-001",
"fingerprint_hash": "64-character-sha256-hex-digest",
"raw_text": "Extracted page text"
}Success response:
{
"success": true,
"id": "note uuid",
"token": "paperloom jwt",
"dashboard_path": "/dashboard/[hash]",
"educational_gap_id": "gap uuid",
"missing_concepts_count": 4,
"verified_resources_count": 6,
"render_workspace": null
}The route validates input with Zod, uses the Supabase service role key only on the server, runs Tavily-grounded Gemini analysis, stores the audit, and returns a JWT scoped to the page fingerprint.
Creates a Render workspace for an existing note. Requires a bearer token minted by PaperLoom.
Headers:
Authorization: Bearer <paperloom-jwt>
Content-Type: application/json
Request body:
{
"fingerprint_hash": "64-character-sha256-hex-digest",
"note_id": "note uuid"
}The token fingerprint_hash claim must match the requested workspace.
Stores scanned page content.
iduser_idfingerprint_hashraw_textcreated_at
Stores the latest curriculum audit for a scanned page.
idnote_idmissing_conceptsverified_resourcesupdated_at
PaperLoom uses fingerprint-bound access rather than broad client database permissions.
/api/ingestusesSUPABASE_SERVICE_ROLE_KEYserver-side only.- The browser never receives the service role key.
- The ingest route signs a short-lived JWT using
SUPABASE_JWT_SECRET. - JWT claims include
fingerprint_hash,device_id,role, andscope. - The dashboard creates a Supabase client with the custom JWT.
- RLS allows access only when
auth.jwt() ->> 'fingerprint_hash'matches the row fingerprint. educational_gapsauthorization is inherited through the parentbooks_and_notesrow.
Current JWT lifetime is 10m in app/api/ingest/route.ts.
The student dashboard at app/dashboard/[hash]/page.tsx includes three workspace profiles:
standard: the complete dashboard with document overview, scanned text, gaps, sources, and practice.bionic: a spatial reading view that expands line height and letter spacing while emphasizing word stems and key learning terms.horizon: a focus environment that removes surrounding dashboard content and presents exactly one learning node at a time.
This layer is designed to reduce cognitive overload, help students recover from attention drift, and let learners move between broad review and single-task study without leaving the workspace.
The wokwi/ folder contains an ESP32 scanner simulation.
Hardware simulation:
- ESP32 DevKit C
- SSD1306 OLED over I2C
- Push button on GPIO 18
- Mock fiber sensor readings
- SHA-256 fingerprint generation with
mbedtls
Before running the simulation, update INGEST_ENDPOINT in wokwi/sketch.ino:
const char *INGEST_ENDPOINT = "https://your-app.vercel.app/api/ingest";The simulator connects to Wokwi-GUEST, generates a fingerprint, posts the scan payload, and displays upload status on the OLED.
Set this environment variable to enable automatic Render service creation after ingest:
RENDER_AUTODEPLOY_WORKSPACES=truePaperLoom creates a Render static-site service with these environment variables:
PAPERLOOM_FINGERPRINT_HASHPAPERLOOM_NOTE_IDPAPERLOOM_WORKSPACE_MODENEXT_PUBLIC_PAPERLOOM_NOTE_ID
Render deployment is optional. The core PaperLoom scan and dashboard flow works without it.
npm run dev # Start local development server
npm run build # Build the Next.js application
npm run start # Start the production server
npm run lint # Run ESLint
npm run typecheck # Run TypeScript without emitting filesOn Windows PowerShell, if script execution blocks npm, use npm.cmd:
npm.cmd run lint
npm.cmd run typecheck- Apply Supabase migrations before deploying the app.
- Store service-role, Gemini, Tavily, Render, and JWT secrets only in server-side environment variables.
- Use HTTPS for browser and ESP32 ingest calls.
- Keep PaperLoom JWT expiry short.
- Rotate
SUPABASE_JWT_SECRETcarefully because it must match Supabase JWT verification expectations. - Verify RLS policies after every schema change.
- Disable Render auto-deploy unless per-student static workspace creation is needed.
- The dashboard depends on a valid fingerprint route parameter.
- Client dashboard data access intentionally goes through Supabase RLS, not through a privileged API route.
- The browser scan console stores the PaperLoom token in
sessionStorageunderpaperloom.jwtandpaperloom_token. - The dashboard can also read compatible token keys from cookies for deployment variants.
NEXT_OUTPUT_MODE=exportenables static export mode innext.config.mjs; API routes require a server runtime and should not be used from a purely static deployment.