Elara
A career workspace where a resume is a projection of one career profile rather than another copy of it — so correcting a fact once corrects every resume.
- Category
- Career Workspace
- Type
- Personal Project
- Year
- 2026
- Status
- Deployed
- Role
- Solo Developer
- Technologies
- Next.js, React, TypeScript, Tailwind CSS, PostgreSQL, Prisma, LLM APIs
- Index
- 03 / 05

01Overview
ELARA keeps a career profile, resumes, a job search and an application tracker in one place, so an application starts from something already written down rather than a blank page. It is a personal project. Both external integrations are optional: the whole product — signup, email verification, password reset, resumes, PDF export — runs end to end on a fresh clone with no third-party account at all.
02Problem
A resume is usually a copy. Keep four of them, fix a job title in one, and the other three quietly go stale, so the document you actually send is whichever one you last remembered to edit. Tailoring makes that worse rather than better: every variant aimed at a posting is one more copy to keep current. And the tools that offer to help will cheerfully hand back an employer, a percentage or a seniority you never had — which is the single failure that costs you the room when somebody checks.
03Approach
Facts live once, on the career profile, and a resume is a projection of it. A resume owns ordering, visibility and its own wording overrides; it never owns the facts, so correcting a job title corrects every resume at once. Sections are opt-out rather than opt-in — a resume records what to leave out, so anything added to the profile later turns up in resumes that already exist instead of silently going missing. The assistant is permitted to rewrite wording and nothing else, and that rule is enforced rather than requested: a prompt is guidance, so the one invention that does real damage on a resume is also checked for afterwards.
04System
Next.js App Router, split into three route groups. The marketing pages are statically rendered; the auth pages stand alone; everything in the workspace sits under a layout that calls requireUser(), so no page can forget the guard. Business logic lives in seven services — Career, Resume, Job, Application, Ai, Email, Pdf — and components only render. Server actions are the single boundary every mutation crosses: validate with Zod, check ownership, delegate. Postgres through Prisma holds every record. The piece that matters most is the projection: it is a pure function with no server import, so the browser runs it for the live preview and the server runs the same one for the export, and the two cannot drift.
- 01
Surface
- Marketing · AuthThe landing page renders statically. A non-secret elara_signed_in cookie lets the navigation greet a signed-in visitor without forcing a session lookup and turning the page dynamic.
- WorkspaceProfile, resumes, jobs, saved and applications. requireUser() runs in the group layout, so no page below it can forget the guard.
- Resume editorThree panes: what to include, the page at A4, and how it is set. Runs the projection in the browser so the preview updates as you type, with debounced autosave keyed per field.
- 02
Boundary
- Server actionsThe one boundary every mutation crosses. Validates with Zod, scopes each query by userId, and returns a single ActionState shape — so a mismatched owner updates nothing rather than relying on a check that could be skipped.
- PDF route/api/resume/[id]/pdf on the Node runtime. No session returns 401; an id you do not own returns 404 rather than 403, so ids cannot be probed for existence.
- 03
Services
- Career · Job · ApplicationBusiness logic, server-only. Profile records, the job search and the tracker, whose stage changes are written as events rather than overwriting a field. Components render and actions delegate; nothing reaches Prisma from a component.
- Ai · EmailBoth optional. Without an OpenAI key the assistant reports that it is not configured; without a Resend key verification and reset links are written to the server log instead of being delivered.
- Resume · PdfThe export half of the projection path. PdfService runs the same pure function the editor ran, then draws it with @react-pdf/renderer at point sizes on a true A4 page — selectable, searchable, with working links, and entries marked so a single role never splits across a page break.
- 04
Data
- PostgreSQL · PrismaProfile, resumes, jobs, applications, sessions and AI usage. Usage is recorded whether or not a call succeeded, so a failing loop is visible rather than invisible.
- OpenAI · ResendThe model behind the assistant, and transactional email. Neither is required to run the product; each absence is reported rather than papered over.
05Interface
The workspace carries the product's own sequence as its navigation — 01 profile, 02 resumes, 03 jobs, 05 applications — so the numbering is a map rather than decoration. The resume editor is three panes: what to include, the page itself at A4, and how it is set. Profile sections reorder with move up and move down rather than drag, so reordering works from a keyboard, with a screen reader and on a phone; the application board does support dragging, but every card also carries a plain select for its stage, which makes dragging the shortcut instead of the only route. Nothing that can be measured is guessed. Profile completion is a weighted checklist that names the three heaviest things still missing, and a posting states how many of the skills it names are already on your profile, with the missing ones dashed and a line underneath saying ELARA will not add them for you.




06Build
- A resume is a projection of the profile, not a copyFacts live once. A resume owns ordering, visibility and per-resume wording; change a job title and every resume follows. The projection is a pure function with no server import, which is why the editor can run it in the browser for a live preview while the server runs the same one for the PDF — the preview cannot drift from the export because there is only one implementation.
- Sections are opt-out, not opt-inA resume records what to leave out rather than what to include, so something added to the profile later appears in resumes that already exist. The opposite default fails silently: you add a certification, and the four resumes you already made never mention it.
- The assistant is checked, not just instructedThe rule is that it rewrites wording and never invents record. That is in the system prompt and repeated per task, but a prompt is not a guarantee — so any suggestion containing a figure that was not in the input is dropped before it is shown. Numbers are the fabrication that does real damage on a resume and the one class that is cheap to detect. When the filter fires, the interface says so rather than hiding it.
- Sessions are opaque tokens, not JWTsOnly a SHA-256 digest is stored, so a database leak hands out no working sessions and logging out revokes server-side immediately. A separate non-sensitive flag cookie exists purely so the marketing pages can stay statically rendered while still greeting a signed-in visitor.
- Templates are code, not rowsThere is no ResumeTemplate table because it would hold nothing the registry does not already own. A resume stores a template key, and an unknown key falls back to the default rather than failing to render — which also means adding a template is a file, not a migration.
07Current Status
Deployed on Vercel. The landing and auth pages are live; signing in on the hosted instance currently returns a server error, so the workspace screens here were captured from a local instance running the seeded demo account. The 16 job listings are invented and flagged isDemo, so real imports could be told apart later — there is no ingestion pipeline. Both integrations degrade in the open rather than silently: without an OpenAI key the assistant is hidden, and without a Resend key verification and reset links are printed to the server console, which is what lets signup, email verification and a password reset all complete on a fresh clone with no third-party account. 65 tests across five files cover the projection, the completion weighting, the skill match, date formatting and the Latin-1 fold; typecheck, lint and build pass. The known limits are written down rather than hidden: the PDF embeds no fonts, so it covers Latin-1 only and a name in another script would not render; the rate limiter holds its buckets in one process, which protects a single instance and not several behind a load balancer; and page breaks in the on-screen preview are indicative, with the PDF's own layout engine as the authority. There are no users beyond my own account.