Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pokodoc

An anime-inspired notes app — Notion's flexibility + Word's document feel + a soft pastel visual identity. Full original product spec: instructions.md.

Monorepo (npm workspaces): a standalone API and a fully static frontend, deliberately decoupled so the frontend can later be embedded in Tauri (desktop) or a mobile shell with no changes.

apps/api   Hono + Drizzle + Neon Postgres — REST API, owns the database
apps/web   Next.js Pages Router, output: 'export' — static frontend, RTK Query data layer

Quick start

npm install                                  # from repo root — installs both workspaces

cp apps/api/.env.example apps/api/.env.local
# edit apps/api/.env.local: DATABASE_URL (Postgres), JWT_ACCESS_SECRET, REFRESH_TOKEN_TTL_DAYS,
# and optionally SUPER_ADMIN_EMAIL — see apps/api/README.md for details

npm run db:migrate                           # applies apps/api/drizzle/*.sql to DATABASE_URL

npm run dev:api                              # apps/api  → http://localhost:4000
npm run dev:web                              # apps/web  → http://localhost:3000 (separate terminal)

apps/web calls the API at NEXT_PUBLIC_API_URL (defaults to http://localhost:4000 if unset).

Full backend reference (every endpoint, request/response shapes, data model, roles, deploying to Vercel) lives in apps/api/README.md — that file is kept up to date and is the source of truth for the API contract.

Architecture

  • Hierarchy: Notebook → Page → Sub-page. A Notebook is just a container (no title/icon of its own — shown in the UI via a preview of its first Page). A Page belongs to one notebook and is independently favoritable/searchable. A Sub-page (a Page with a parentPageId) is reachable only by navigating from its parent's content — never independently favorited or searched.
  • Frontend is a static export (output: 'export' in next.config.js) — no Server Actions, no API routes, no SSR. Every page fetches client-side via RTK Query after mount. Dynamic per-record pages (/doc, /notebook, /public-doc, /public-notebook) are addressed with a ?id= query string rather than a [id] path segment, because a dynamic path segment needs getStaticPaths at build time and note/notebook ids are unknown user content — a query-string route is one static HTML file that works for any id.
  • Editor: BlockNote (block-based, slash-command menu). Internal sub-page links are plain /doc/?id=<id> hrefs inside the block content; clicks on them are intercepted (components/Editor.tsx) so navigation stays client-side instead of a full reload or a new tab (BlockNote's own link handling opens new tabs by default — the interception has to happen on mousedown, not click, to beat it).
  • State: Redux Toolkit + RTK Query (apps/web/lib/store.ts, apps/web/lib/apiSlice.ts). A second, separate createApi instance (publicApiSlice) handles the unauthenticated /public/* endpoints so guest requests never go through the authenticated reauth-on-401 pipeline.
  • Design system: Tailwind, Material3-derived color tokens (see tailwind.config.jsprimary, primary-container, surface, on-surface-variant, etc., lifted from a Stitch export) plus custom type-scale tokens (display-lg, h1, h2, body-md, label-md, …). Literata (serif, headings/body) + Inter (sans, UI labels/captions) via next/font, self-hosted at build time (no external font CDN — matters for the offline Tauri/mobile embed). components/ui/Input.tsx / Button.tsx are the standard form primitives — reach for these instead of one-off input/button classNames.
  • Sidebar (components/AppShell.tsx): a floating pill nav, collapsed by default, opened by a pencil-icon toggle (Framer Motion). Treated as visually finalized — don't restyle it without being explicitly asked to.

Auth & roles

Custom access-token + refresh-token JWT (no third-party auth provider) — access tokens are short-lived (15 min) JWTs, refresh tokens are opaque random strings stored hashed and rotated on every use, with reuse/theft detection. Full detail in apps/api/README.md's "Auth" section.

Four roles:

  • Guest — no account. Can view notebooks their owner has explicitly marked public (/public-notebook, /public-doc), read-only.
  • Author (default for every signup) — full control of their own notebooks/pages only.
  • Maintainer — moderation only: can view any notebook (including private, for support/moderation) and unpublish someone else's public notebook, nothing else.
  • Super Admin — full read/write on any notebook/page, plus /admin/users (change roles, block/unblock — blocking takes effect immediately, not after token expiry).

The first Super Admin is bootstrapped via the SUPER_ADMIN_EMAIL env var (apps/api/.env.local) — sign up or log in with that exact email to become super_admin.

What's built (as of 2026-07-26)

  • Notebooks list (home page), a notebook's page feed, single page/sub-page view, Favorites (list/grid toggle), Search (live, debounced), Settings, empty-state "Recent Chats" shell.
  • Full page editor: BlockNote content, autosave (700ms debounce), emoji/decorative-icon picker, contentEditable title, reading-time + relative "Updated Xm ago" timestamps, sub-page creation, delete-with-confirm.
  • Auth: signup/login/logout, silent refresh-on-load, route guarding, /forgot-password (UI only — no email-sending backend yet, shows an honest "not available yet" message on submit).
  • RBAC: role management UI at /admin/users, public/private notebook toggle + shareable link.

Known gaps / not built yet

  • No notebook delete/archive endpoint — pages can be archived (DELETE /pages/:id), but there's no equivalent for notebooks. Test notebooks from backend verification runs currently can't be cleaned up through the API.
  • No password reset backend/forgot-password is a real page but can't actually send an email (no email provider integration). Would need a Marketplace decision (e.g. Resend) before building the real flow.
  • No email verification, OAuth/social login, or rate limiting on auth — explicitly out of scope so far.
  • Recent Chats (/chats) is an empty-state shell only. Real AI chat (LangGraph + AI SDK streaming) is planned for a later phase — see "Future phases" reasoning below.
  • Google Stitch MCP was connected (stitch MCP server, project-scoped) but its tool listing was failing server-side (can't resolve reference #/$defs/ScreenInstance) as of last check — a Stitch-side bug, not something fixable locally. Current visual design was built by hand matching pasted Stitch HTML exports instead of live MCP calls.
  • Tauri/mobile packaging hasn't been started — the static-export architecture exists specifically to make this possible later, but no Tauri project exists yet.

Why Hono (not NestJS) for the backend

apps/api is Hono specifically because MCP (@modelcontextprotocol/sdk) and LangGraph.js are both framework-agnostic — they mount as routes/handlers in any Node backend — and Hono already runs natively on Vercel Functions with streaming/SSE/WebSocket support. When the "let AI agents act on Pokodoc on a user's behalf" phase and the real Recent Chats backend get built, they bolt onto this same apps/api service with no framework migration.

Conventions worth knowing before touching this codebase

  • Verify against the real database, not mocks. DATABASE_URL in apps/api/.env.local points at a live Neon instance — every backend change in this project has been verified with real curl calls against it, including security-critical paths (role boundaries, token reuse/theft detection, immediate block effect). Keep doing that.
  • No Chrome DevTools MCP or Playwright MCP without the user explicitly asking first. Frontend changes are verified with next build (static export must succeed) and careful code review instead.
  • Don't run unnecessary shell commands — the user has asked for this directly; keep Bash usage purposeful (a build check, a migration, a targeted grep), not exploratory chains.
  • The sidebar (components/AppShell.tsx) is finalized — don't restyle it as a side effect of other work.
  • A next build failure that mentions Could not find files for X in build-manifest.json or "Export encountered errors" is very likely a transient race (usually from a concurrently running npm run dev touching .next), not a real code problem — rm -rf .next and rebuild before assuming something's actually broken.
  • Don't fabricate data or fake success states. Where a real backend/feature doesn't exist yet (password reset emails, the "Shared" notebooks tab, chat), the UI says so honestly (e.g. "isn't available yet" / "coming soon") rather than pretending it worked.

About

notion clone, Pokodoc

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages