nyxcore-systems
9 min read

Bringing Documentation to Life: Markdown, Math, and Mermaids in Our Project Dashboard

We just wrapped a crucial dev session, integrating a dynamic docs tab, complete with advanced Markdown, Mermaid diagrams, and LaTeX math rendering, directly into our project dashboard. Here's how we did it, and the unexpected hurdles we overcame.

DocumentationMarkdownMermaidLaTeXtRPCNext.jsGitHub APIDeveloper ExperienceLessons Learned

Documentation often feels like a necessary chore, but what if it could be a vibrant, interactive part of your development workflow? That's the question we aimed to answer in our latest sprint. Our goal: integrate a fully-featured documentation tab into our project dashboard, supporting advanced markdown, mathematical equations, and even Mermaid diagrams, all while consolidating our project intelligence into a structured, accessible format.

It was an ambitious session, but by the end of it, we had a fully functional docs tab, 20 new documentation sections neatly organized, and a few hard-earned lessons under our belt.

The Mission: A New Home for Project Intelligence

Our primary objective was three-fold:

  1. A Dedicated Docs Tab: Create a new section within our project dashboard to host all project-related documentation.
  2. Rich Content Support: Implement rendering for advanced Markdown features, specifically LaTeX-style math and Mermaid diagrams.
  3. Structured Knowledge Base: Migrate and organize our existing "intelligence" documents into a clean docs/ folder, each as an individual Markdown file.

This wasn't just about moving files; it was about transforming how we access and interact with our project's foundational knowledge.

Under the Hood: Crafting the Docs Experience

Building this involved touching several layers of our stack, from the backend API to the frontend rendering engine.

Backend: The GitHub Connector & tRPC

To populate our docs tab, we needed a way to list and fetch Markdown files directly from the project's linked GitHub repository. This is where our github-connector and tRPC router came into play.

We augmented src/server/trpc/routers/projects.ts with a new docs sub-router:

  • docs.list({ projectId }): This procedure leverages fetchRepoTree from our github-connector to recursively scan the repository. It then filters for files residing in the docs/ directory and ending with .md, returning a neat list of { path, name } pairs.
  • docs.get({ projectId, filePath }): Given a filePath, this procedure fetches the raw content of the Markdown file. Critically, it also intelligently extracts the document's title (from the first # heading) and a concise summary (from the first paragraph).
typescript
// Simplified snippet from src/server/trpc/routers/projects.ts
import { fetchRepoTree, fetchRepoFileContent } from 'github-connector';
import { z } from 'zod';
import { publicProcedure, router } from '../trpc';

export const projectsRouter = router({
  // ... other project procedures
  docs: router({
    list: publicProcedure
      .input(z.object({ projectId: z.string() }))
      .query(async ({ input }) => {
        // Assume we get owner/repo from projectId
        const tree = await fetchRepoTree(input.projectId);
        return tree
          .filter(item => item.path.startsWith('docs/') && item.path.endsWith('.md'))
          .map(item => ({ path: item.path, name: item.path.replace('docs/', '').replace('.md', '') }));
      }),
    get: publicProcedure
      .input(z.object({ projectId: z.string(), filePath: z.string() }))
      .query(async ({ input }) => {
        const content = await fetchRepoFileContent(input.projectId, input.filePath);
        // Extract title and summary using regex (details below!)
        const titleMatch = content.match(/^#\s(.+)/);
        const title = titleMatch ? titleMatch[1] : 'Untitled Document';

        // The regex for summary needs to handle multiline content,
        // which led to an interesting lesson...
        const summaryMatch = content.match(/^([\s\S]+?)(?:\n\n|\n#|$)/);
        const summary = summaryMatch ? summaryMatch[1].trim().split('\n')[0] : 'No summary available.';

        return { title, summary, content };
      }),
  }),
});

Frontend: The Markdown Powerhouse

The src/components/markdown-renderer.tsx component became the focal point for bringing our rich content to life.

  • Math Support: We integrated remarkMath and rehypeKatex to enable seamless rendering of LaTeX-style math. Now, $inline$ equations and $$block$$ formulas render beautifully.
  • Mermaid Diagrams: To support visual diagrams, we created a MermaidDiagram component. This dynamically imports mermaid (to keep our bundle size lean) and renders language-mermaid code blocks as SVG via dangerouslySetInnerHTML. Crucially, we ensured it respects our dark theme.
  • Smart GitHub Links: Internal links within our documentation (e.g., src/server/some-file.ts) are automatically rewritten by a RepoLink component to point to the correct GitHub blob URLs, making navigation between code and docs effortless.
  • Performance: We wrapped the components object passed to the Markdown renderer in useMemo to prevent unnecessary re-renders, ensuring a smooth user experience.
  • Props: The renderer now accepts optional githubOwner and githubRepo props to facilitate the link rewriting.

User Interface: The Docs Tab in Action

The src/app/(dashboard)/dashboard/projects/[id]/page.tsx now proudly hosts our new DocsTab component. We added a <TabsTrigger value="docs"> alongside our existing tabs, marked with a BookOpen icon from Lucide.

The DocsTab itself offers a smooth user journey:

  1. List Grid: An initial view showing all available documentation sections as a grid of cards, fetched via trpc.projects.docs.list.
  2. Summary Card: Clicking a card reveals a summary and the option to view the full document.
  3. Full Doc View: The complete Markdown content, rendered beautifully with all its math, diagrams, and smart links, fetched via trpc.projects.docs.get.

Dependencies

To make all this magic happen, we pulled in a few new libraries:

  • mermaid
  • remark-math
  • rehype-katex
  • katex

The Intelligence Within: Our Consolidated Knowledge Base

Perhaps the most extensive part of the session, though less code-intensive, was the migration of our project's core intelligence. We moved 20 distinct sections from disparate temporary files into a structured docs/ folder, each with its own dedicated Markdown file (e.g., docs/12-auto-fix-pipeline.md, docs/13-refactor-pipeline.md).

These documents cover critical aspects of our system, including:

  • Auto-Fix Pipeline: Our 4-phase automated issue resolution system.
  • Refactor Pipeline: How we categorize and manage code refactors.
  • Code Analysis: Details on our file indexer, pattern types, and quality scoring.
  • Action Points: The lifecycle of manual, LLM-extracted, and auto-detected action items.
  • Discussion Service: Our multi-mode, language-aware consensus rounds.
  • GitHub Connector: Deep dive into our BYOK, ghFetch, fetchRepoTree, and write operations.
  • Analytics Dashboard: The architecture behind our 15 parallel queries and 10 interactive panels.
  • Injection Diagnostics: Context measurement, unresolved variable detection, and sanitization.

Having all this knowledge centrally located and beautifully rendered significantly enhances discoverability and understanding for anyone working on the project.

Lessons from the Trenches: Bumps on the Road to Completion

No development session is complete without a few head-scratching moments. These "pain points" often turn into the most valuable lessons.

1. The Stubborn NPM Cache: EACCES Woes

  • Problem: While running npm install, I repeatedly hit EACCES errors, indicating permission issues with root-owned files in my ~/.npm/_cacache/ directory. This is a classic "npm cache owned by root from a previous sudo npm install" scenario.
  • Failed Attempts: npm cache clean --force followed by npm install didn't fix it. Attempting sudo chown in a non-interactive shell environment is often risky and best avoided if possible.
  • Solution: The most reliable workaround was to direct npm to use a temporary cache directory:
    bash
    npm install --cache /tmp/npm-cache-nyxcore
    
    This allowed npm to operate cleanly without touching the problematic system cache.
  • Takeaway: When facing persistent EACCES errors with npm, especially in automated or non-interactive environments, sidestep the issue by specifying a temporary, user-owned cache directory rather than wrestling with system-level permissions.

2. Regex /s Flag - A TypeScript Gotcha

  • Problem: For extracting the first paragraph as a summary, I initially tried a regex like /^(.+?)(?:\n\n|\n#|$)/s. The /s flag (dotall mode) is incredibly useful for matching newlines with .
  • Failed Attempt: TypeScript immediately threw TS1501: 's' flag is not allowed in 'target' 'ES2017' and earlier. Our project's tsconfig.json was set to an older target.
  • Solution: The classic workaround for dotall behavior in older JavaScript/TypeScript environments is to replace . with [\s\S]. This matches any whitespace or non-whitespace character, effectively acting as dotall.
    typescript
    const summaryRegex = /^([\s\S]+?)(?:\n\n|\n#|$)/;
    
  • Takeaway: Be mindful of your TypeScript --target setting when using modern regex flags. If you're constrained by an older target, [\s\S] is your trusty substitute for the /s (dotall) flag.

3. The LLM Context Window Wall

  • Problem: When attempting to use a large language model (LLM) agent to merge three existing doc parts and then generate nine entirely new, complex sections, the prompt quickly became "too long." The sheer volume of input source files and the complexity of the output overwhelmed the model's context window.
  • Failed Attempt: A single, monolithic LLM agent tasked with too broad a scope.
  • Solution: We broke down the problem. Instead of one giant agent, we used:
    • Parallel Agents with Narrow Scopes: One agent focused on generating sections 12-16, another on 17-20. This reduced the input context for each.
    • Dedicated Bash Agent: A separate, simpler agent was tasked specifically with splitting the initial /tmp/nyxcore-doc-part{1,2,3}.md files into their individual 11 sections.
  • Takeaway: For complex LLM-driven tasks, especially those involving significant input or output generation, decomposition is key. Break down the problem into smaller, manageable sub-tasks that fit within the model's context window. Utilize specialized agents or scripts for distinct phases like file manipulation or focused content generation.

Current State and What's Next

As of now, our development server is running smoothly on port 3000, all changes are committed and pushed to origin/main, and the branch is clean. The temporary npm cache at /tmp/npm-cache-nyxcore is safe to delete.

Our immediate next steps include:

  • End-to-end verification of the Docs tab on a project with a linked GitHub repo containing docs/*.md files.
  • Confirming that Mermaid SVG renders correctly in dark theme, checking for any Flash Of Unstyled Content (FOUC) or double-initialization issues.
  • Considering the addition of a docs/00-index.md file to serve as a README, listing all 20 sections with internal links for easy navigation.

The existing MarkdownRenderer callsites that don't pass githubOwner/githubRepo work fine as the props are optional, so no action is needed there.

Conclusion

This session was a fantastic demonstration of how investing in internal tooling and documentation infrastructure can significantly enhance developer experience. By making our project's intelligence easily discoverable, interactive, and beautifully rendered, we're not just storing information; we're empowering our team to build better, faster, and with a deeper understanding of our system. The journey was full of learning, and the outcome is a testament to the power of thoughtful development.


json
{
  "thingsDone": [
    "Implemented Projects Docs tab",
    "Added mermaid diagram support",
    "Added math (LaTeX) markdown rendering",
    "Moved all 20 intelligence docs into `docs/` as individual section files",
    "Developed `docs.list` tRPC procedure to fetch repo tree and filter markdown files",
    "Developed `docs.get` tRPC procedure to fetch file content, extract title and summary",
    "Integrated `remarkMath` and `rehypeKatex` into `markdown-renderer.tsx`",
    "Created `MermaidDiagram` component with dynamic import and dark theme support",
    "Modified `CodeBlockWrapper` to delegate `language-mermaid` to `MermaidDiagram`",
    "Implemented `RepoLink` component for rewriting internal links to GitHub blob URLs",
    "Added optional `githubOwner` and `githubRepo` props to `MarkdownRendererProps`",
    "Optimized `markdown-renderer.tsx` with `useMemo` for components object",
    "Integrated `DocsTab` component into dashboard page with 3 states (list grid, summary card, full doc view)",
    "Installed `mermaid`, `remark-math`, `rehype-katex`, `katex` dependencies"
  ],
  "pains": [
    "Encountered `EACCES` with root-owned npm cache, leading to `npm install --cache /tmp/npm-cache-nyxcore` workaround.",
    "Experienced `TS1501` when using `/s` regex flag in TypeScript, requiring `[\s\S]` workaround.",
    "Hit LLM context window limits when attempting to merge/generate too many doc sections with a single agent, necessitating parallel agents and scope splitting."
  ],
  "successes": [
    "Successfully implemented a dynamic and rich documentation viewing experience.",
    "All project intelligence documents are now centralized and accessible.",
    "Overcame critical technical hurdles with practical workarounds and improved patterns.",
    "Enhanced developer experience by providing interactive diagrams and math within docs.",
    "Streamlined internal link navigation to GitHub source code."
  ],
  "techStack": [
    "Next.js",
    "React",
    "tRPC",
    "TypeScript",
    "Markdown",
    "Mermaid.js",
    "KaTeX",
    "remark",
    "rehype",
    "GitHub API",
    "Lucide Icons",
    "npm"
  ]
}