From 167d95c5e8d40285ea2c12ee8be70518933726b4 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Thu, 20 Aug 2026 16:57:30 +0700 Subject: [PATCH] =?UTF-8?q?feat(web):=20full=20layout=20overhaul=20?= =?UTF-8?q?=E2=80=94=20Linear=20design=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete UI/UX overhaul, not just style changes: Layout: - Dark-mode-first (near-black #08090a canvas, whiteOpacity borders) - Sticky header with brand + nav + theme toggle - Sticky sidebar (xl+) with hierarchical doc tree - Main content in max-w-3xl centered column - Inter font system (via @font-face + CSS vars) - Font feature settings cv01/ss03 for Linear-geometric Inter Components rewritten in Linear aesthetic: - Homepage: editorial-style doc listing with tags, section headers - Doc page: breadcrumb-style nav links, metadata bar (author/date/tags), clean prose, Related + History sections - TOC: rehype-slug heading anchors + github-slugger matching - Create/Edit: inline form with Linear-style inputs/buttons - Login: centered card-style, brand-indigo CTA button - Docs index: sectioned listing with tag previews - Search: dark-themed search with result cards + snippets - ThemeToggle: system-default + localStorage persistence Typography: - prose with custom Tailwind classes matching Linear's: headings #f7f8f8 font-light, body #d0d6e0, links #7170ff with hover #828fff, code blocks #191a1b bg with #23252a border New deps: rehype-slug (heading anchors), github-slugger (TOC matching) Files: layout.tsx (overhaul), page.tsx, create/, login/, docs/, [section]/[...slug]/page.tsx, Sidebar.tsx, ThemeToggle.tsx, TOC.tsx, DocForm.tsx, Markdown.tsx, Search page --- .hermes/plans/phase11-crud-auth-ui.md | 110 ++++++++-------- apps/web/app/[section]/[...slug]/page.tsx | 109 ++++++++++------ apps/web/app/components/DocForm.tsx | 88 ++++--------- apps/web/app/components/Markdown.tsx | 16 ++- apps/web/app/components/Sidebar.tsx | 66 ++++++++++ apps/web/app/components/ThemeToggle.tsx | 13 +- apps/web/app/create/page.tsx | 33 +++-- apps/web/app/docs/page.tsx | 42 ++++--- apps/web/app/globals.css | 46 +++++-- apps/web/app/layout.tsx | 53 +++++--- apps/web/app/login/page.tsx | 72 ++++++----- apps/web/app/page.tsx | 97 +++++++------- apps/web/app/search/page.tsx | 147 +++++++++++----------- 13 files changed, 517 insertions(+), 375 deletions(-) create mode 100644 apps/web/app/components/Sidebar.tsx diff --git a/.hermes/plans/phase11-crud-auth-ui.md b/.hermes/plans/phase11-crud-auth-ui.md index 0835736..58c1cb5 100644 --- a/.hermes/plans/phase11-crud-auth-ui.md +++ b/.hermes/plans/phase11-crud-auth-ui.md @@ -1,67 +1,67 @@ -# MCPedia Phase 11 — CRUD + Auth + UI/UX Improvement +# MCPedia Phase 11 — CRUD + Auth + Web UI + +## STATUS: ✅ ALL DONE (committed f8b525d, CI+Deploy success) ## Plan (spec BEFORE implementation, per user rule) ### Scope: 4 areas 1. **CRUD**: Create/Read/Update/Delete documents from web UI + MCP -2. **Auth**: Session-based auth for web edits; API key auth for MCP writes -3. **UI/UX**: Polish doc page, edit forms, search UX, dark mode -4. **Agent integration**: MCP tools for CRUD with auth +2. **Authentication**: MCP/API writes (x-webhook-secret), Web CRUD (cookie-based ADMIN_PASSWORD) +3. **Web + Agent access**: Web forms + MCP write tools +4. **UI/UX**: Edit forms, TOC, dark mode -## Requirements: -1. **Source of truth is the filesystem** — each doc is a markdown file under `content/{section}/{slug}.md` with YAML frontmatter (parsed/striped by gray-matter). -2. **DB mirrors disk** — `documents` table has slug/title/section/body/status/tags/etc; `document_chunks` stores embeddings; `document_revisions` snapshots body+meta. -3. **Single indexing path** — `indexContentFile` in `@mcpedia/core` writes DB + revisions + chunks; CRUD ops call it (or its pieces) after writing the file. -4. **Auth** — MCP/API writes require `x-webhook-secret` matching `WEBHOOK_SECRET` (Phase 4 pattern). Web CRUD uses cookie-based auth (Phase 11 addition: `ADMIN_PASSWORD`). -5. **Slug rules** — `[a-z0-9][a-z0-9/_-]*`, no `//`, no path traversal (`..`). The slug in the URL is `
/`. -6. **No breaking existing features** — read tools/routes unchanged; existing 32 tests still green. -7. **UI/UX** — edit button on doc pages (auth-gated), `?edit=1` inline form, `/create` page, login page, table of contents auto-generated from h2, dark mode toggle. +### Requirements: +1. Source of truth = filesystem (markdown files in content/{section}/{slug}.md) +2. DB mirrors disk (documents, document_chunks, document_revisions tables) +3. Single indexing path: indexContentFile in @mcpedia/core +4. Auth: MCP/API writes use WEBHOOK_SECRET; Web uses ADMIN_PASSWORD cookie +5. Slug rules: [a-z0-9][a-z0-9/_-]*, no //, no .. traversal +6. No breaking existing features (32 original tests still green) +7. UI/UX: edit button on doc pages, ?edit=1 inline form, /create page, login, TOC, dark mode -### Schema changes: -- Optional: `users` table if auth is user-based -- OR: simple `WEB_AUTH_TOKEN` env for admin access (simpler, fits single-author KB) +### Implementation -### Backend surface: -- **Web auth**: Cookie-based session OR single admin token in localStorage - - Simpler approach: `POST /api/auth/login` (checks `ADMIN_PASSWORD` env) → sets cookie - - Edit UI gated behind cookie check in server components -- **MCP auth**: `x-api-key` header for write tools (create/update/delete) - - Read tools (list, get, search) — public - - Write tools (create, update, delete, index_document, reindex_all, restore_revision) — require key - - `createMcpServer(authKey?)` threads the header, same pattern as `x-webhook-secret` +#### Backend +- `packages/parser`: added `stringifyFile()` (writes markdown with frontmatter) +- `packages/core`: `createDocument`, `updateDocument`, `deleteDocument` (file I/O + DB + revision + chunks) +- `apps/api`: tRPC CRUD routers (`requireWriteAuth`), fixed `requireWriteAuth` env-constant bug (now uses `ctx.expectedSecret` injected from `createApp(deps)`) +- `apps/mcp`: 3 new write tools (`create_document`, `update_document`, `delete_document`) gated by `x-webhook-secret` -### CRUD operations: -- **Create**: `POST /trpc/createDocument` (title, slug, section, body, tags) - - Writes markdown file to `content/{section}/{slug}.md` - - Triggers indexer (enqueues job or calls directly) -- **Update**: `POST /trpc/updateDocument` (slug, body, title, tags, status) - - Updates file + creates revision + reindexes -- **Delete**: `POST /trpc/deleteDocument` (slug) - - Removes file, DB rows, chunks; creates revision tombstone -- All mutations require auth (web cookie OR webhook secret OR MCP api-key) +#### Web UI +- `apps/web/app/api/auth/login/route.ts`: POST login → verify ADMIN_PASSWORD, set `mcpedia_admin` cookie +- `apps/web/app/api/docs/route.ts`: POST (create) +- `apps/web/app/api/docs/[...slug]/route.ts`: PUT (update), DELETE (delete) +- `apps/web/app/components/DocForm.tsx`: shared create/edit form +- `apps/web/app/create/page.tsx`: create form +- `apps/web/app/login/page.tsx`: login form +- `apps/web/app/[section]/[...slug]/page.tsx`: `?edit=1` inline edit, TOC, dark mode, Edit button +- `apps/web/app/docs/page.tsx`: docs index listing +- `apps/web/app/components/TOC.tsx`: auto-generated TOC from h2/h3 headings +- `apps/web/app/components/ThemeToggle.tsx`: dark mode toggle (localStorage + system default) -### UI improvements: -- Edit button on doc pages (auth-gated) → link to `/docs/{slug}/edit` -- Create page: `/docs/create` with form (section dropdown, slug, title, tags, markdown editor) -- Edit page: `/docs/{slug}/edit` pre-fills from getDocument -- Search page: live search results with keyboard nav, better empty states -- Doc page: table of contents (auto-generated from h2), dark mode toggle +#### New deps (minimal — only for UX): +- `rehype-slug` (heading anchors for TOC links) +- `github-slugger` (matching slug algorithm for TOC client-side) -### Verification steps: -1. TDD: write failing tests for each new API endpoint/method first -2. `bun run typecheck` — green -3. `bun run test` — 32 existing + new tests green -4. `bun run build` — green -5. CI + Deploy passes -6. Browser: create doc → search sees it → edit doc → changes reflect → delete doc → gone -7. MCP: create_document tool with key → doc appears; without key → unauthorized +### Gotchas (learned the hard way) +1. tRPC fetch adapter expects input directly as JSON body, NOT JSON-RPC envelope +2. requireWriteAuth compared `ctx.webhookSecret !== WEBHOOK_SECRET` (module-level env constant) — untestable. Fixed: `ctx.webhookSecret !== ctx.expectedSecret` (injected per-app via deps). +3. Next.js catch-all routes: `[...slug]/edit/` is INVALID (catch-all must be last). Used `?edit=1` query param instead. +4. Next.js App Router: PUT/DELETE on `/api/docs/route.ts` doesn't match `/api/docs/{slug}` — need dynamic route `/api/docs/[...slug]/route.ts`. +5. `@env.example` should be updated. +6. `ADMIN_PASSWORD` must be set in VPS `.env` (deployed separately). -### Acceptance criteria: -- [ ] createDocument tRPC + MCP -- [ ] updateDocument tRPC + MCP -- [ ] deleteDocument tRPC + MCP -- [ ] Web auth (cookie-based login) -- [ ] MCP write-tool auth (api-key) -- [ ] UI: edit/create pages -- [ ] UI: TOC + dark mode on doc pages -- [ ] All tests green, CI+Deploy passes +### Verification +- Typecheck: ✅ 4/4 apps green +- Tests: ✅ 40 tests green (32 original + 8 new), no DB/Redis +- Build: ✅ web compiled +- CI: ✅ success → Deploy: ✅ success +- Live: all 9 endpoints 200, 13 MCP tools live, CRUD e2e verified (login → create → view → delete via cookie auth), MCP create_document verified via header auth +- Test docs cleaned up (404 confirmed) + +### Commits +1. `57f9001` feat: Phase 11 — CRUD + auth + web UI +2. `1dd16eb` feat(web): Phase 11 UI/UX — TOC, dark mode toggle, /docs index +3. `98437cb` fix(web): /api/docs accepts cookie OR header (not both required) +4. `8ed8c67` fix(web): split PUT/DELETE into /api/docs/[...slug]/route.ts +5. `f8b525d` chore: remove test docs diff --git a/apps/web/app/[section]/[...slug]/page.tsx b/apps/web/app/[section]/[...slug]/page.tsx index f88a15b..29eeb52 100644 --- a/apps/web/app/[section]/[...slug]/page.tsx +++ b/apps/web/app/[section]/[...slug]/page.tsx @@ -7,10 +7,7 @@ import Markdown from "@/components/Markdown"; import DocForm from "@/components/DocForm"; import TOC from "@/components/TOC"; -// Render at request time. The content lives in Postgres (populated by the -// indexer/worker), which is not available at build time (CI has no DB), so we -// opt out of static generation. At this corpus scale request-time rendering is -// instant. +// Render at request time (content in Postgres, not available at build time). export const dynamic = "force-dynamic"; interface DocPageProps { @@ -25,15 +22,22 @@ export default async function DocPage({ params, searchParams }: DocPageProps) { const doc = await getDocument(fullSlug); if (!doc) notFound(); - // Check auth for edit mode. const cookieStore = await cookies(); const canEdit = cookieStore.get("mcpedia_admin")?.value != null; - // If ?edit=1 and authenticated → show the edit form. + // Edit mode: inline form if (edit === "1" && canEdit) { return ( -
-

Edit: {doc.title}

+
+ + ← Back to {doc.title} + +

+ Edit: {doc.title} +

-
- - ← Back +
+
+ + MCPedia + + / + + Docs -

- {doc.title} -

-
- {doc.tags.map((t) => `#${t}`).join(" ")} · {doc.author || "unknown"} -
{canEdit && ( - - Edit - + <> + / + + Edit + + )}
- +

{doc.title}

- +
+ {doc.author || "unknown"} + • + {new Date(doc.updatedAt).toLocaleDateString()} + {doc.tags.map((t) => ( + + #{t} + + ))} +
+ +
+ + +
{related.length > 0 && ( -