Engineering
How closedNote works
Technical overview of the architecture, features, and design decisions behind closedNote — the only prompt manager that tracks how your prompts evolve.
Samuel Aboderin
Computer Engineering · UNILAG · v1.1
Why closedNote?
PromptBase stores prompts. Notion organizes them. FlowGPT shares them. None of them remember how they got there.
In real life, prompts evolve. You tweak your "code review prompt" three times, and by the fourth iteration you've forgotten what made version 2 actually work. closedNote is built on one thesis: a prompt is not a sticky note — it's a document with a history.
Beyond versioning, closedNote adds structure: prompts organized into collections, chained into multi-step workflows, refined by AI, and importable from any image via OCR — all private by default, all in one place.
Version History
newEvery time a user saves an edit to a prompt, closedNote snapshots it into a prompt_versions table. A version history panel on the prompt detail page shows the full timeline. Clicking any version renders a live diff against the current content using Google's diff-match-patch library — additions in green, removals in red.
Restoring a version updates the prompt content without creating a new version entry — preserving the history chain exactly as it was. A new version is only created when the user edits and saves content that differs from the last saved state.
| Behaviour | Creates new version? |
|---|---|
| Save with changed content or title | Yes |
| Save with no changes | No |
| Restore a previous version | No |
| Edit after restore, content differs | Yes |
Key files
- supabase/migrations/004_prompt_versions.sql- table + RLS policies
- lib/promptData.ts- savePrompt() with skipVersion flag
- app/api/prompts/[id]/versions/route.ts- authenticated GET endpoint
- components/VersionHistory.tsx- timeline + diff UI
Architecture
closedNote is a Next.js 14 App Router application with a Supabase backend. Almost all pages are client components; only the docs page is server-rendered.
Frontend
- Next.js 14 — App Router, RSC
- React 18 — hooks, client components
- Tailwind CSS 3.4 — utility-first styling
- TypeScript 5.5 — full type safety
- diff-match-patch — version diff engine
Backend
- Supabase — PostgreSQL + Auth
- PKCE flow — secure auth
- Row Level Security — per-user isolation
- Vercel — edge deployment
Key files
- lib/hooks/usePrompts.ts- data fetching hook
- lib/promptData.ts- prompt CRUD + versioning
- lib/chainData.ts- chain CRUD
- components/AuthProvider.tsx- auth context
- components/VersionHistory.tsx- version timeline + diff
- app/api/chat/route.ts- AI refinement proxy
- app/api/ocr/route.ts- OCR endpoint
- app/api/prompts/[id]/versions/route.ts- version history endpoint
OCR & AI Refinement
Upload a screenshot, photo, or scan — GPT-4o Vision extracts the text, and the AI refinement step restructures it into a clean, reusable prompt. A Tesseract.js fallback handles offline cases.
Online (GPT-4o Vision)
- Printed & handwritten text
- High accuracy on complex layouts
- Requires an OpenAI API key
Offline (Tesseract.js)
- Runs entirely in the browser
- No API key required
- Works offline and on flights
The app attempts the online route first and silently falls back to Tesseract if the API is unavailable or unconfigured.
AI Provider
Chain runs and chat refinement use HuggingFace's Zephyr-7B by default — free, no billing required. If you add your own OpenAI key in Settings, it takes priority and unlocks GPT-4o Mini for all AI features.
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co# required
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...# required
OPENAI_API_KEY=sk-...# optional — enables GPT-4o OCR + AI
HUGGINGFACE_API_KEY=hf_...# optional — free AI fallback
Database
Six tables in PostgreSQL via Supabase. All have RLS enabled — every query is automatically scoped to the authenticated user.
| Table | Purpose |
|---|---|
| users | Auth profile, synced from Supabase Auth on signup |
| prompts | Title, content, model, collection, user_id |
| prompt_versionsnew | Versioned snapshots of each prompt — powers the diff view |
| tags | Many-to-many; cascade-deletes with prompt |
| prompt_chains | Titled sequences of steps, owned by user |
| chain_steps | Ordered steps with content and output variables |
Security
Row Level Security
All tables enforce RLS. Every query is automatically filtered to the authenticated user's data at the database level — including prompt_versions.
PKCE Auth Flow
Supabase Auth uses PKCE (Proof Key for Code Exchange). Sessions are stored ephemerally — cleared when the browser closes.
API key privacy
User-supplied OpenAI keys are stored only in localStorage and passed per-request. They are never persisted server-side.
Authenticated API routes
The /api/prompts/[id]/versions endpoint validates the user's JWT before any query. No RLS bypass is possible.
HTTPS
All traffic is encrypted in transit. Enforced by Vercel on production.
Deployment
Optimised for Vercel, but runs on any platform that supports Next.js 14.
- 1
Fork the repo
Clone or fork from GitHub: github.com/aboderinsamuel/closedNote
- 2
Create a Supabase project
Run all four migration files in order from supabase/migrations/ via the Supabase SQL Editor. The fourth migration (004_prompt_versions.sql) creates the version history table.
- 3
Set environment variables
Add NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. Optionally add OPENAI_API_KEY for GPT-4o OCR.
- 4
Deploy to Vercel
Connect the repo, add the env vars, and deploy. Update your Supabase Auth redirect URLs to match your production domain.
Contributing
closedNote is open source. Whether you're fixing a typo or building a feature, contributions are welcome.
Built by Samuel Aboderin · Computer Engineering · UNILAG