Skip to content

aevum-orrin/Inkwell

Repository files navigation

Inkwell — A Web-Native Knowledge Garden

Markdown notes, backlinks, and a graph view in your browser.

Motivation

Obsidian is wonderful and I do not own my notes inside it. Notion is hosted and I cannot grep my notes. I wanted the Obsidian editing experience — [[wikilinks]], backlinks panel, graph view — in a thing I can self-host and share a folder of with collaborators in one click.

Inkwell is a small wiki / personal-knowledge-graph engine. Markdown is the source of truth, but the wiki view is collaborative, searchable, and indexed.

What it does

  • Edit Markdown in the browser with a live side-by-side preview; [[wikilinks]], GFM tables, and code blocks.
  • Backlinks panel and a force-directed graph view of the link structure.
  • Multi-user workspaces with roles (owner / editor / reader) enforced by Postgres row-level security. New users create their first workspace in the app — no manual SQL.
  • Passwordless sign-in: a magic link, with a 6-digit code fallback for when a mail scanner eats the link.
  • "Who's here" presence on every page, and a sidebar page list that updates in realtime as collaborators save.
  • Full-text search via Postgres tsvector; recently-edited pages rank higher.
  • Page history: every edit is recorded as an append-only revision.
  • Light / dark theme (follows your OS, with a manual toggle) and a mobile-friendly layout.

Not yet: live co-editing of the same page body (two people typing into one page will overwrite each other on save — a CRDT is on the roadmap), a workspace switcher UI (the app opens your oldest workspace), and the Git mirror worker.

Architecture

   React + Vite editor ──► Supabase (auth + realtime presence)
        │
        ▼
   Postgres
     ├── pages      (markdown, frontmatter, tsvector)
     ├── links      (page_id, target_slug)   ← recursive CTE for the graph view
     ├── revisions  (append-only history)
     └── RLS        (workspace roles are the security boundary)

There is no application server: the browser talks to Supabase directly and row-level security is the security boundary. See docs/ARCHITECTURE.md for the longer tour.

Stack

react · vite · typescript · react-markdown · d3-force · supabase (postgres, auth, realtime) · vitest

Quick start (local)

Requires Node ≥ 20 and the Supabase CLI (plus Docker) for the local database.

npm ci                             # install into ./node_modules
cp .env.example .env.local         # fill in Supabase URL + anon key
npx supabase init                  # first time only; keeps existing migrations
npx supabase start                 # local Postgres + auth, prints URL and keys
npx supabase db reset              # runs migrations 0001→0008 + seed
npm run dev                        # http://localhost:5173

The seed creates a demo workspace with two pages. After your first magic-link sign-in, add yourself to it from the SQL editor:

insert into workspace_members (workspace_id, user_id, role)
values ('00000000-0000-0000-0000-000000000001', auth.uid(), 'owner');

Or skip the seed entirely and just sign in — the app's onboarding screen creates a workspace for you, the same flow production uses.

No Docker? Create a free hosted Supabase project instead and follow steps 1–2 of the Vercel guide below, pointing .env.local at it.

Testing

npm test            # vitest, single run
npm run test:watch  # watch mode
npm run typecheck   # tsc --noEmit
npm run lint        # eslint, zero warnings allowed
npm run test:db     # apply migrations to a real Postgres and assert RLS

npm test covers the wikilink parser and slugger, the XSS-safe search snippet renderer, presence subscription lifecycles, page create/save and link reconciliation, the editor's flush-on-exit, workspace creation, and static guards over the SQL (RLS enabled everywhere, pinned search_path, SECURITY DEFINER on the policy helper and revision trigger, clamped RPC parameters).

npm run test:db is the one that executes the migrations: it boots a throwaway Postgres (or uses $DATABASE_URL), applies 0001→0008, and asserts the real RLS behaviour — bootstrap ownership, revision-on-save, owner reads, the search/graph RPCs, and full non-member/anon isolation. It needs initdb/psql on PATH (e.g. brew install postgresql), and runs in CI against a postgres:16 service (.github/workflows/ci.yml).

Deploy to Vercel

The app is a static Vite build; Supabase provides the database, auth, and realtime. vercel.json already contains the SPA rewrite (deep links like /p/some-page would 404 without it), asset caching, security headers, and a Content-Security-Policy.

0. Pick the right branch. The application code lives on the Inkwell-setup branch; main is only a README. When you import the repo (step 4) set Vercel's Production Branch to Inkwell-setup, or merge Inkwell-setup into main first. Otherwise production builds an empty tree.

1. Create the Supabase project. On supabase.com create a project and note, from Settings → API, the Project URL and the anon public key.

2. Apply the schema. Migrations 0001 → 0008, in order, either:

  • CLI: npx supabase link --project-ref <ref> then npx supabase db push (run npx supabase init first — the repo ships no config.toml), or
  • Dashboard: paste each file in supabase/migrations/ (0001 → 0008, in order) into the SQL editor and run it.

0008 is required, not optional — it fixes two row-level-security faults (without it every signed-in user gets an error and every page save is rejected) and enables Realtime for the page list. Don't run supabase/seed.sql against production; its demo workspace has no members and is invisible under RLS.

3. Configure Supabase Auth (Authentication in the dashboard):

  • URL Configuration → set Site URL to your production domain (https://inkwell-yourname.vercel.app) and add https://<domain>/** to Redirect URLs. The /** matters: sign-in preserves the page you were on, and previews need their own pattern (e.g. https://*-<team>.vercel.app/**).

  • Email → SMTP Settingsconfigure custom SMTP (Resend, Postmark, or SES). This is required for real users: Supabase's built-in sender is rate-limited to a few mails/hour and only delivers to your own team addresses, so strangers never receive their magic link. Set SPF, DKIM, and DMARC on the sending domain or the mail lands in spam.

  • Email Templates → Magic Link → include the code so the 6-digit fallback works when a corporate mail scanner consumes the link:

    <p>Your Inkwell sign-in code: <strong>{{ .Token }}</strong></p>
    <p>Or click to sign in: <a href="{{ .ConfirmationURL }}">Sign in</a></p>

4. Import the repo into Vercel. Add New → Project, pick the repository. The Vite preset is auto-detected (npm run builddist/); vercel.json pins it anyway. In Settings → Environments, set the Production Branch to Inkwell-setup (step 0).

5. Set the environment variables (Settings → Environment Variables, for Production and — if you want working previews — Preview):

Name Value
VITE_SUPABASE_URL Project URL from Settings → API
VITE_SUPABASE_ANON_KEY anon public key from Settings → API

The anon key is safe to expose to browsers — row-level security is the actual boundary. If a variable is missing the deployed app shows a setup hint instead of a blank page. Changing these later requires a redeploy (Vite inlines them at build time). Public previews point at the same database, so either use a separate Supabase project for Preview or enable Vercel deployment protection.

6. Deploy and sign in. Open the site, request a magic link (or use the code), and the app walks you through creating your first workspace — you become its owner automatically. Every later user just signs in and creates or is added to a workspace; an owner adds others via:

insert into workspace_members (workspace_id, user_id, role)
select w.id, u.id, 'editor'
  from workspaces w, auth.users u
 where w.slug = '<workspace-slug>' and u.email = 'them@example.com';

7. (Recommended) Keep it alive and backed up. Free Supabase projects pause after 7 idle days and have no automatic backups. .github/workflows/supabase-keepalive-backup.yml handles both, twice a week, once you add the repo secrets SUPABASE_URL, SUPABASE_ANON_KEY, and (for dumps) SUPABASE_DB_URL. It is inert until those are set.

Security notes

  • Every table has row-level security; roles come from workspace_members. The policy helper and the revision trigger are SECURITY DEFINER with a pinned search_path (see supabase/migrations/0008), verified by npm run test:db.
  • Search snippets render as text — only the <mark> highlights from search_pages become elements, so authors can't inject HTML into other users' results. react-markdown runs without rehype-raw.
  • A Content-Security-Policy (vercel.json) restricts scripts to 'self' and connections to your Supabase project over HTTPS and WSS. It allows the default *.supabase.co host; if you put Supabase behind a custom domain or self-host it, update connect-src in vercel.json to that API/Realtime host or every request is blocked.
  • Signup is open: anyone can create an account and a workspace. Existing data stays protected by RLS, but if you want invite-only, disable email signups in Supabase. Consider enabling Supabase Auth CAPTCHA against email-bombing.

Status

Pre-alpha, deployable. Working: page CRUD, wikilinks, backlinks, full-text search, graph view, presence, revisions, multi-user RLS, in-app onboarding, light/dark, mobile layout. Next: CRDT co-editing, Git mirror worker, workspace switcher UI.

Layout

src/
  lib/         supabase client, wikilink parser, slugify, snippet renderer, theme
  hooks/       data hooks (usePage, useWorkspace, useGraph, useSearch, …)
  components/  Editor, PageList, BacklinksPanel, GraphView, SearchBar,
               Onboarding, ThemeToggle, ErrorBoundary, AppShell, …
  pages/       route components (HomePage, PageView, GraphPage, LoginPage)
  test/        vitest setup, supabase mock, migration guard tests
supabase/
  migrations/  0001 pages → 0008 RLS fixes + realtime
  seed.sql     demo workspace + two pages (local dev only)
  test/        real-Postgres migration + RLS integration test (npm run test:db)
.github/
  workflows/   CI (lint/test/build + DB integration) and keep-alive/backup
vercel.json    SPA rewrites, caching, security headers, CSP

About

Inkwell: web-native knowledge garden

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors