From 251a6e9b4dd8d215420e5c3b6f4ad919fe45f4e7 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Fri, 21 Aug 2026 10:29:35 +0700 Subject: [PATCH] =?UTF-8?q?refactor:=20centralize=20section=20config=20?= =?UTF-8?q?=E2=80=94=20remove=20all=20hardcoded=20section=20lists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New packages/config/src/sections.ts: SECTIONS, SECTIONS_BY_ID, SECTION_IDS (no Node.js built-in imports โ€” safe for client components) - New package entrypoint: @mcpedia/config/sections (exports only sections.ts) - Client components import from @mcpedia/config/sections (no node:fs in bundle) - Server components import from @mcpedia/config (has env/fs, server-only) - Removed hardcoded SECTIONS/SECTION_LABEL/SECTION_ICON from: - [section]/page.tsx (was inline array) - [section]/[...slug]/page.tsx (was hardcoded Record maps) - Sidebar.tsx (was inline array) - layout.tsx (was inline array) - page.tsx (was inline array) - create/page.tsx (was inline array via SECTIONS import) - Added @mcpedia/config/sections entrypoint to package.json exports --- apps/web/app/[section]/[...slug]/page.tsx | 108 +++++++++++---------- apps/web/app/[section]/page.tsx | 15 +-- apps/web/app/components/Sidebar.tsx | 10 +- apps/web/app/create/page.tsx | 30 +++--- apps/web/app/globals.css | 14 +++ apps/web/app/layout.tsx | 52 ++++------- apps/web/app/page.tsx | 109 +++++++++------------- packages/config/package.json | 3 +- packages/config/src/index.ts | 20 ++-- packages/config/src/sections.ts | 44 +++++++++ 10 files changed, 202 insertions(+), 203 deletions(-) create mode 100644 packages/config/src/sections.ts diff --git a/apps/web/app/[section]/[...slug]/page.tsx b/apps/web/app/[section]/[...slug]/page.tsx index 06b27b1..b3df3fa 100644 --- a/apps/web/app/[section]/[...slug]/page.tsx +++ b/apps/web/app/[section]/[...slug]/page.tsx @@ -7,6 +7,7 @@ import Markdown from "@/components/Markdown"; import DocForm from "@/components/DocForm"; import TOC from "@/components/TOC"; import { classifyPath, extractFoldersForSection } from "@mcpedia/core"; +import { SECTIONS_BY_ID } from "@mcpedia/config/sections"; import type { DocumentMeta } from "@mcpedia/core"; // Render at request time (content in Postgres, not available at build time). @@ -17,19 +18,22 @@ interface DocPageProps { searchParams: Promise<{ edit?: string }>; } -const SECTION_LABEL: Record = { - docs: "Documentation", - writeups: "Writeup", - research: "Research", - notes: "Note", -}; - -const SECTION_ICON: Record = { - docs: "๐Ÿ“„", - writeups: "๐Ÿ“", - research: "๐Ÿ”ฌ", - notes: "๐Ÿ“Œ", -}; +/** + * Get section label and icon from the centralized config. + */ +function getSectionInfo(section: string) { + const info = SECTIONS_BY_ID.get(section); + if (!info) { + return { + label: section.charAt(0).toUpperCase() + section.slice(1), + icon: "๐Ÿ“„", + }; + } + return { + label: info.label, + icon: info.icon, + }; +} /** * Render any extra/custom frontmatter fields as labeled badges. @@ -167,10 +171,7 @@ function FolderIndexPage({ const folders = [...immediateFolders.entries()].sort(([a], [b]) => a.localeCompare(b)); - const sectionInfo = { - icon: SECTION_ICON[section] || "๐Ÿ“", - label: SECTION_LABEL[section] || section, - }; + const sectionInfo = getSectionInfo(section); return (
@@ -366,6 +367,8 @@ export default async function DocPage({ params, searchParams }: DocPageProps) { const related = await getRelated(fullSlug, 5); const revisions = await listRevisions(fullSlug, 10); + const sectionInfo = getSectionInfo(doc.section); + return (
{/* Breadcrumb โ€” dynamic based on the doc's actual path */} @@ -380,7 +383,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) { const crumbPath = doc.slug.split("/").slice(0, i + 1).join("/"); const isLast = i === doc.slug.split("/").length - 1; const label = i === 0 - ? SECTION_LABEL[part] || part + ? sectionInfo.label : part .split(/[-_]/) .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) @@ -408,12 +411,12 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
- {SECTION_ICON[doc.section] || "๐Ÿ“„"} + {sectionInfo.icon}
- {SECTION_LABEL[doc.section] || doc.section} + {sectionInfo.label} ยท @@ -475,38 +478,41 @@ export default async function DocPage({ params, searchParams }: DocPageProps) { Related Documents
- {related.map((r) => ( - -
- - {SECTION_ICON[r.section] || "๐Ÿ“„"} - - -
-

- {r.title} -

-
- {r.tags.slice(0, 3).map((t) => ( - - #{t} + {related.map((r) => { + const rInfo = getSectionInfo(r.section); + return ( + +
+ + {rInfo.icon} - ))} -
- - ))} + +
+

+ {r.title} +

+
+ {r.tags.slice(0, 3).map((t) => ( + + #{t} + + ))} +
+ + ); + })}
)} diff --git a/apps/web/app/[section]/page.tsx b/apps/web/app/[section]/page.tsx index 8044b98..00eb55c 100644 --- a/apps/web/app/[section]/page.tsx +++ b/apps/web/app/[section]/page.tsx @@ -1,5 +1,6 @@ import Link from "next/link"; import { listDocuments } from "@mcpedia/core"; +import { SECTIONS } from "@mcpedia/config/sections"; export const dynamic = "force-dynamic"; @@ -12,18 +13,6 @@ interface DocMeta { path: string; } -const SECTIONS: { id: string; label: string; icon: string; desc: string }[] = [ - { - id: "docs", - label: "Documentation", - icon: "๐Ÿ“„", - desc: "Setup guides, API references, and protocol specs.", - }, - { id: "writeups", label: "Writeups", icon: "๐Ÿ“", desc: "Post-mortems, debugging stories, and case studies." }, - { id: "research", label: "Research", icon: "๐Ÿ”ฌ", desc: "Deep-dive analysis, architecture notes, and experiments." }, - { id: "notes", label: "Notes", icon: "๐Ÿ“Œ", desc: "Quick references, patterns, and gotchas." }, -]; - /** * Build a hierarchical folder tree from a flat list of documents. * Each node is either a folder (has children) or a leaf doc. @@ -68,8 +57,6 @@ function buildFolderTree(docs: DocMeta[], section: string): TreeNode[] { if (isLeaf) { node.doc = doc; node.docCount = 1; - } else { - // This is a folder โ€” count will be accumulated below } current = node.children; } diff --git a/apps/web/app/components/Sidebar.tsx b/apps/web/app/components/Sidebar.tsx index cd5e098..cc77421 100644 --- a/apps/web/app/components/Sidebar.tsx +++ b/apps/web/app/components/Sidebar.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { SECTIONS } from "@mcpedia/config/sections"; interface Doc { slug: string; @@ -11,13 +12,6 @@ interface Doc { tags: string[]; } -const SECTIONS = [ - { id: "docs", label: "Documentation", icon: "๐Ÿ“„" }, - { id: "writeups", label: "Writeups", icon: "๐Ÿ“" }, - { id: "research", label: "Research", icon: "๐Ÿ”ฌ" }, - { id: "notes", label: "Notes", icon: "๐Ÿ“Œ" }, -] as const; - /** * Build a hierarchical folder tree from flat document slugs. * Each node is either a folder (has children) or a leaf doc. @@ -132,7 +126,7 @@ function renderTreeNode( {node.children.length > 0 && (
    - {node.children.map((child) => renderTreeNode(child, 0, pathname))} + {node.children.map((child) => renderTreeNode(child, depth + 1, pathname))}
)} diff --git a/apps/web/app/create/page.tsx b/apps/web/app/create/page.tsx index 2c7aae7..d027b0c 100644 --- a/apps/web/app/create/page.tsx +++ b/apps/web/app/create/page.tsx @@ -1,33 +1,31 @@ import { listDocuments, extractFoldersForSection } from "@mcpedia/core"; +import { SECTIONS } from "@mcpedia/config/sections"; +import { WEBHOOK_SECRET } from "@mcpedia/config"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import DocForm from "@/components/DocForm"; -import { WEBHOOK_SECRET } from "@mcpedia/config"; - -export const dynamic = "force-dynamic"; export default async function CreatePage() { const cookieStore = await cookies(); - const canEdit = cookieStore.get("mcpedia_admin")?.value != null; - if (!canEdit) redirect("/login"); + const isAdmin = cookieStore.get("mcpedia_admin")?.value != null; + if (!isAdmin) redirect("/login"); - const allDocs = await listDocuments(); - const docPaths = allDocs.map((d) => d.path); - - // Build folder suggestions per section using the shared core helper - const sections = ["docs", "writeups", "research", "notes"]; - const foldersBySection: Record = {}; - for (const sec of sections) { - foldersBySection[sec] = extractFoldersForSection(docPaths, sec); - } + const all = await listDocuments(); + const docPaths = all.map((d) => d.path); return (
-

Create Document

+
+ โœ๏ธ +

Create Document

+
+ [s.id, extractFoldersForSection(docPaths, s.id)]) + )} />
); diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 2a04b3b..7ddc037 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -72,3 +72,17 @@ body { .prose tbody tr:nth-child(even) { background: rgba(15, 16, 17, 0.5); } + +/* Inline code in prose */ +.prose code { + font-family: var(--font-mono); + font-size: 0.85em; +} + +/* Blockquote styling */ +.prose blockquote { + border-left: 2px solid var(--color-border); + margin: 1.5em 0; + padding: 0.25em 1em; + font-style: italic; +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 761277c..1ef208f 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import "./globals.css"; import ThemeToggle from "@/components/ThemeToggle"; import Sidebar from "@/components/Sidebar"; +import { SECTIONS } from "@mcpedia/config/sections"; export const metadata: Metadata = { title: "MCPedia โ€” Knowledge base for humans and AI agents", @@ -10,51 +11,34 @@ export const metadata: Metadata = { "A content-first knowledge base. Humans read the Web UI; AI agents use the MCP server. Both share one Core.", }; -const SECTIONS = [ - { id: "docs", label: "Documentation", icon: "๐Ÿ“„" }, - { id: "writeups", label: "Writeups", icon: "๐Ÿ“" }, - { id: "research", label: "Research", icon: "๐Ÿ”ฌ" }, - { id: "notes", label: "Notes", icon: "๐Ÿ“Œ" }, -] as const; - export default function RootLayout({ children, -}: { +}: Readonly<{ children: React.ReactNode; -}) { +}>) { return ( +