Personal·2026·Web App · Knowledge Management
Atlas — Personal Research Workspace
Capture sources, connect topics, test claims against evidence and write knowledge you can revisit — a private web app I designed end to end, from the schema to the security model

- Role
- Solo developer — design, database, front end, back end, security, testing and deployment
- Team
- Solo project
- Duration
- Oct 2026
- Year
- 2026
Key figures
- 35
- Prisma models (including join tables)
- 55
- unit tests passing (11 files)
- 14
- topic relation types
- 50 MB
- maximum attachment size
- 30 days
- session lifetime
Atlas is a private web app for researching one subject across many sources. You drop links into an Inbox with a comment, then file them under topics, connect them, take notes and write reports in a rich-text editor. Each claim keeps its statement, the evidence for or against it, and your own conclusion apart. It has workspace-scoped FULLTEXT search, a knowledge graph, export/import and installs as a PWA. There is one owner and no public sign-up, AI, queue or third-party login. I designed the screens, the database (35 Prisma models) and the security layer myself, and it now runs in production on a host that uses Phusion Passenger.
The problem
- 01Research ends up scattered across bookmarks, half-watched videos, PDFs and notes, so coming back later you can’t tell which source supports which conclusion.
- 02General note apps don’t separate a claim from its evidence, and don’t say how sure you are of the conclusion.
- 03Capturing links is the risky part: the server has to fetch URLs a user supplies, which opens the door to SSRF, and attachments can be malicious files or HTML that runs scripts.
- 04Long writing across several tabs, or a session that expires mid-edit, can overwrite work or lose text without you noticing.
The approach
- 01Capture: paste a URL with a comment, topic, tags and priority. URLs are normalised and checked for duplicates, and metadata for YouTube, GitHub and generic web pages is fetched through adapters — a failed fetch never blocks saving.
- 02Inbox → Topics: file many sources under a topic, tag and priority at once with keyboard shortcuts. Topics have aliases, pins, favourites, collections, 14 relation types, and transactional merging of duplicates.
- 03Notes and reports use TipTap: headings, bold, links, images, code blocks, tables, checklists and callouts, with autosave and a visible status. Optimistic concurrency stops another tab overwriting your edit. Reports keep explicit versions that can be viewed and restored as a new version.
- 04Claims separate the statement, the stance of each piece of evidence (supports, contradicts, neutral, context) and your own conclusion, with a 0–100 confidence. Research tasks carry a priority and due date and can be tied to a topic, source or claim.
- 05A monochrome knowledge graph with React Flow and dagre: filter by relation and tag, search for a topic, view 1–2 hop neighbourhoods, with rendering capped at 150 topics and 500 edges. Workspace-scoped FULLTEXT search and a command palette (Ctrl/⌘+K).
- 06Security: a single owner created on first run, Argon2id passwords, opaque 30-day sessions stored only as SHA-256 hashes, login throttling, every query scoped to the workspace, every mutation checking the request origin, plus a CSP and anti-framing headers.
- 07Attachments live in a private S3 bucket. Size (50 MB maximum), MIME type, extension and binary signature are checked, a SHA-256 is stored, downloads use 2-minute signed links, there is an authenticated server-upload fallback, and HTML previews are sanitised, sandboxed and covered by a CSP.
- 08Export/import: versioned JSON and a Markdown ZIP. Imports are transactional, remap IDs, merge existing source URLs, keep report versions and sanitise snapshots. It installs as a PWA (manifest, service worker, offline page) and ships with a multi-stage Dockerfile and GitLab CI.
The result
- 01One workspace that goes from link → topic → claim and evidence → versioned report, with all data in a database I control and exportable at any time.
- 0255 unit tests in 11 files pass, covering URLs, SSRF, safe fetching, passwords and sessions, files, HTML sanitising, import, origins, PWA and error handling. The repo also has integration tests against a real MySQL database and Playwright end-to-end tests.
- 03GitLab CI runs lint, typecheck, tests and a build on every push, and the app runs for real at atlas.phatphum.me (sign-in required).
Deliverables
- A Next.js 16 + Prisma/MySQL web app (35 models, migration and FULLTEXT indexes)
- Inbox, Topics, Sources, Notes, Claims, Tasks, Reports, Graph, Collections, Tags, Trash and Settings
- The TipTap editor and versioned reports
- The security layer: Argon2id, hashed sessions, safe fetching, sanitised and sandboxed previews, CSP
- JSON and Markdown ZIP export/import
- PWA support, a multi-stage Dockerfile, sample S3 policy and nginx config, and GitLab CI
- Unit, integration (MySQL) and end-to-end (Playwright) tests
Gallery
Owner setup, capture and the inbox (demo workspace)
16:9 / LG
First-run setup: create the single owner with a password of at least 12 characters. Once an account exists the page closes — there is no public registration

Dashboard: workspace totals, topics in progress, and what deserves a second look — an unverified claim, an overdue task, a source awaiting review

Inbox: select several sources and file them under a topic, tag and priority in one go, with j/k to move, Space to select, e to archive, t for topic and # for tag

Capture (press C): paste a URL with a comment, topic, tags and priority. Duplicates are detected and metadata is fetched when possible — a failed fetch never blocks saving
Retrospective
Challenges
- Fetching metadata from user-supplied URLs is an SSRF surface: DNS is checked on every hop, the approved IP is pinned to the connection, private ranges and IPv4-mapped IPv6 are rejected, every redirect is validated, and size and time are capped (2 MB, 5 redirects, 8 seconds) — with dedicated tests.
- A rich-text editor must not accept arbitrary HTML, so documents are validated against a whitelist of TipTap nodes and marks, restricted attributes and link protocols, and untrusted HTML files are previewed in a separate response with its own sandbox and CSP.
- Avoiding overwrites across tabs and on session expiry: optimistic concurrency, unsaved text kept in the editor, and a way to sign in from another tab without losing what you were writing.
- I had to downgrade Prisma from 7 to 6 during deployment so it would run on the Plesk/Passenger host (per the commit history).
Learnings
- Design security in layers — input validation, workspace scoping, request origin, CSP, sandboxing — rather than relying on one check.
- A model that separates claim, evidence and conclusion makes research notes more honest than free-form text, because you have to say what is fact and what is opinion.
- Testing against a real database is more trustworthy than mocking it, but needs a guard so tests can never touch the main one: Atlas’ test helper refuses any database name that doesn’t contain “test” or “shadow”.
Interested in work like this?
Tell me about your brief — I reply within 24 hours.