Busflow Docs

Internal documentation portal

Skip to content

Docs-Hub β€” Auto-Generated Documentation Portal ​

Internal documentation portal that auto-renders all Markdown files in the monorepo, with an integrated AI chat assistant powered by the context-engine.

Problem ​

The predecessor docs-assistant (studio/docs-assistant, now removed) provided a chat-only interface. Team members had to already know what to ask. There was no way to browse, discover, or navigate the documentation organically. The docs-hub solves this by providing a full browsable documentation portal.

Vision ​

A single internal documentation portal that:

  1. Auto-generates a browsable docs site from the monorepo's folder structure β€” zero manual curation
  2. Integrates an AI chat assistant (slide-over drawer) powered by the context-engine for semantic search and Q&A
  3. Supports role-based content visibility so different team roles see different documentation scopes

Critical Decision: Framework Choice ​

VitePress is purpose-built for documentation sites. It auto-generates sidebar navigation from folder structure, includes built-in local search, and supports custom Vue components.

Strengths:

  • Auto-sidebar from file system β€” the core requirement β€” works out of the box
  • Built-in full-text search (MiniSearch) β€” no extra infra needed
  • Theme customization via Vue component slots (layout-bottom, nav-bar-title-after)
  • Extremely fast: instant HMR, sub-second builds
  • Vue-based β€” aligns with the monorepo's ecosystem
  • Chat drawer integrates cleanly via custom theme layout extension

Weaknesses:

  • Static Site Generator only β€” no server-side rendering or server API routes
  • The existing Nuxt server API (/api/chat, /api/doc) cannot live inside VitePress β€” the chat backend must be served separately (from a standalone lightweight Node.js server)
  • Role-based content filtering requires a build-per-role strategy or a runtime content-gate (since VitePress has no server middleware)

Chat integration pattern:

typescript
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import { h } from 'vue'
import ChatDrawer from './components/ChatDrawer.vue'

export default {
  ...DefaultTheme,
  Layout() {
    return h(DefaultTheme.Layout, null, {
      'layout-bottom': () => h(ChatDrawer)
    })
  }
}

Option B: Nuxt Content v3 ​

Nuxt Content is a module for Nuxt that treats Markdown files as queryable data. It provides <ContentNavigation> and <ContentRenderer> components.

Strengths:

  • Full server-side capabilities β€” server API routes, middleware, SSR
  • Role-based filtering can happen server-side via middleware (check auth, filter content)
  • The existing Nuxt server code (/api/chat, session management, RAG) can stay in-place
  • <ContentNavigation> auto-generates sidebar from folder structure

Weaknesses:

  • More complex setup β€” full Nuxt framework overhead for what is essentially a static docs viewer
  • Nuxt Content v3 is still maturing and has breaking changes from v2
  • Slower builds compared to VitePress
  • Content must live in a content/ directory (or be symlinked from the monorepo root)

Option C: Custom Nuxt App (Original Plan β€” Rejected) ​

The original implementation plan proposed building a custom build-time manifest generator, a file-serving API, and a hand-rolled sidebar. This reinvents what VitePress and Nuxt Content already solve.

Why this is wrong:

  • The manifest generator is a worse version of VitePress's built-in file routing
  • The /api/doc endpoint serving raw files at runtime doesn't work on Vercel serverless (no access to monorepo at runtime)
  • Hand-rolling sidebar navigation, search, and markdown rendering is weeks of work for a solved problem
  • No built-in search, no table of contents, no heading anchors β€” all must be manually built

Recommendation ​

Start with VitePress. It solves the primary requirement (auto-generated browsable docs) with near-zero config. The chat drawer integrates via custom Vue components. The chat backend runs on the context-engine β€” a standalone lightweight Node.js server. Role-based visibility can start as build-time content filtering and evolve later.

If future requirements demand server-side auth middleware or dynamic content filtering at runtime, migrate to Nuxt Content v3 at that point. VitePress's Markdown files need zero changes for such a migration β€” both consume standard Markdown with frontmatter.

Why Not Extend a Nuxt App with @nuxt/content? ​

A previous recommendation suggested extending a Nuxt app with @nuxt/content, arguing it reuses auth, RAG, and i18n. This approach couples the docs browser and the AI engine into a single Nuxt monolith. That contradicts the architecture goals:

  1. The context-engine must be a reusable package β€” it powers not only the docs-hub chat but also the future Copilot, Dispatch Assistant, and Customer Support Agent. Embedding it inside a Nuxt app makes it non-portable.
  2. VitePress is purpose-built for docs. Nuxt Content adds unnecessary framework overhead (SSR, middleware, routing) for what is fundamentally a static content viewer.
  3. Separation of concerns. The docs-hub = static site (VitePress). The AI brain = standalone server (packages/context-engine). Different deployment lifecycles, different scaling needs, cleanly decoupled.
  4. Auth and i18n are trivial to add. Password auth can be implemented as a simple client-side gate in VitePress. The original auth and i18n code took ~50 lines combined β€” not enough to justify an entire framework choice.

Architecture ​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     docs-hub (VitePress)                    β”‚
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Auto-Generated Docs  β”‚  β”‚    Chat Drawer (Vue)      β”‚  β”‚
β”‚  β”‚                        β”‚  β”‚                           β”‚  β”‚
β”‚  β”‚  β€’ Sidebar from dirs   β”‚  β”‚  β€’ Floating chat button   β”‚  β”‚
β”‚  β”‚  β€’ Rendered markdown   β”‚  β”‚  β€’ Slide-over panel       β”‚  β”‚
β”‚  β”‚  β€’ Built-in search     β”‚  β”‚  β€’ SSE streaming          β”‚  β”‚
β”‚  β”‚  β€’ Table of contents   β”‚  β”‚  β€’ Source citations        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                        β”‚                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β”‚ HTTP (SSE)
                                         β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚   context-engine (Node.js server)    β”‚
                    β”‚                                      β”‚
                    β”‚  β€’ Gemini embeddings                 β”‚
                    β”‚  β€’ Vector search                     β”‚
                    β”‚  β€’ Claude streaming                  β”‚
                    β”‚  β€’ Role-aware index                  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Deployment Strategy ​

ComponentPhase 1 (Now)Long Term
VitePress static siteVercel (free tier)Hetzner (self-hosted, aligns with infra strategy)
Context-engine serverVercel serverlessHetzner Docker Swarm
AuthenticationGeneral password (current approach)Nhost auth with role detection

Build Trigger & Incremental Builds ​

The docs-hub rebuilds on every commit to main via CI/CD pipeline, with an optimization threshold:

  • Documentation changes (any .md file modified) β†’ always rebuild
  • Code-only changes (< 50 total lines changed, no .md files) β†’ skip rebuild

Ideal future optimization: incremental builds β€” VitePress rebuilds only changed pages. This requires investigating VitePress's caching behavior and potentially a custom build script that detects changed files via git diff and invalidates only affected pages.

Content Pipeline ​

  1. Build time: VitePress reads all .md files from configured source directories. Symlinks or file copies bring monorepo docs into VitePress's content root.
  2. Sidebar: VitePress auto-generates sidebar navigation from the directory tree. A sidebar config in .vitepress/config.ts can customize grouping and ordering.
  3. Search: VitePress's built-in MiniSearch provides instant client-side full-text search across all pages.
  4. Chat: A custom ChatDrawer.vue component calls the context-engine's separate Node.js server for RAG-powered Q&A.

Content Sources ​

The following directories feed into the docs-hub at build time:

Source DirectoryContent TypeAudience
docs/product/Product specs, domain modelAll roles
docs/architecture/Technical architectureDevelopers, Lead
docs/schemas/Database schemas (ERD)Developers
docs/protocols/Protocol specificationsDevelopers
docs/supporting/ADRs, guidelinesDevelopers
apps/*/docs/App-specific docsDevelopers
packages/*/README.mdPackage documentationDevelopers

Context-Engine Package (packages/context-engine/) ​

The context-engine is a standalone reusable package β€” the AI brain behind the docs-hub chat and future product features. It runs as its own lightweight Node.js server, separate from both the VitePress static site and the main NestJS backend.

Package Structure ​

packages/context-engine/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ indexer/              # Document indexing pipeline
β”‚   β”‚   β”œβ”€β”€ walker.ts         # Walks monorepo dirs, reads .md files
β”‚   β”‚   β”œβ”€β”€ chunker.ts        # Splits docs by heading into embeddable chunks
β”‚   β”‚   └── embedder.ts       # Gemini embedding-001 integration
β”‚   β”œβ”€β”€ search/               # Semantic search
β”‚   β”‚   β”œβ”€β”€ vector-store.ts   # Vectra/Qdrant abstraction
β”‚   β”‚   └── similarity.ts     # Cosine similarity, threshold filtering
β”‚   β”œβ”€β”€ chat/                 # LLM answer generation
β”‚   β”‚   β”œβ”€β”€ rag.ts            # Retrieved context β†’ system prompt builder
β”‚   β”‚   └── stream.ts         # Claude SSE streaming
β”‚   β”œβ”€β”€ auth/                 # Simple password auth (MVP)
β”‚   β”‚   └── password.ts       # Shared password validation
β”‚   └── server.ts             # Standalone HTTP server (Fastify)
β”œβ”€β”€ scripts/
β”‚   └── index-docs.ts         # CLI: index all docs β†’ vector store
β”œβ”€β”€ package.json
└── tsconfig.json

API Surface ​

EndpointMethodPurpose
POST /api/authPOSTValidate password, return session token
POST /api/chatPOSTRAG-powered Q&A with SSE streaming
POST /api/searchPOSTSemantic search, return ranked results
GET /api/healthGETHealth check

What Moved from docs-assistant (Completed) ​

Previous LocationCurrent LocationComponent
studio/docs-assistant/scripts/index-docs.tspackages/context-engine/scripts/index-docs.tsDocument indexer
studio/docs-assistant/scripts/upload-index.tspackages/context-engine/scripts/upload-index.tsIndex uploader
studio/docs-assistant/server/utils/rag.tspackages/context-engine/src/chat/rag.tsRAG pipeline
studio/docs-assistant/server/api/chat.post.tspackages/context-engine/src/server.tsChat endpoint
studio/docs-assistant/server/utils/session.tspackages/context-engine/src/auth/password.tsAuth logic

Consumers ​

ConsumerHow it connectsWhen
docs-hub (chat drawer)HTTP β†’ context-engine serverMVP
Customer Support CopilotImport as libraryPhase 2
Dispatch AssistantImport as libraryPhase 2
CI/CD doc validation agentCLI via scripts/Future

MVP Chat Features ​

The chat drawer in the docs-hub provides RAG-powered Q&A with minimal infrastructure.

Password Authentication ​

AspectImplementation
MechanismClient-side password gate in VitePress
FlowUser enters password β†’ validated against context-engine POST /api/auth β†’ session token stored in localStorage
TokenSimple JWT or opaque token with expiry (24h)
ScopeProtects only the chat endpoint, not the docs pages (docs are statically deployed)
FutureReplaced by Nhost auth with role detection when the product goes live

Chat History Persistence (localStorage) ​

Chat conversations persist locally in the browser β€” zero server-side storage for MVP.

typescript
// Stored in localStorage under 'docs-hub-chats'
interface ChatSession {
  id: string            // UUID
  title: string         // Auto-generated from first user message
  messages: Message[]   // Full message history
  createdAt: string     // ISO timestamp
  updatedAt: string     // ISO timestamp
}
FeatureDetail
Storage keydocs-hub-chats
New chatFloating "+ New Chat" button, creates a new session
ContinueSidebar or dropdown lists previous sessions by title
Auto-titleFirst user message truncated to ~50 chars becomes the session title
Size limitCap at 50 sessions; oldest auto-pruned when exceeded
ExportNot in MVP; future option to export as markdown
Cross-deviceNo sync β€” localStorage is per-browser. Acceptable for internal team tool

Role-Based Content Visibility ​

Phase 1: Build-Time Filtering (MVP) ​

Use the existing frontmatter type field (abstract vs concrete) to generate separate builds:

Role TierVisible ContentBuild Command
DeveloperAll contentvitepress build (unfiltered)
Management / Producttype: abstract onlyvitepress build --filter abstract

A pre-build script filters the content directory based on frontmatter before VitePress processes it. Deployed as two separate Vercel projects (or preview URLs).

Phase 2: Runtime Role Filtering (Future β€” with Nhost Auth) ​

When the product reaches production and Nhost auth is integrated:

  • Auth middleware checks session role on every content request
  • Content queries filter by visibility frontmatter field
  • Context-engine indexes documents per role scope (separate embedding indices)

Frontmatter Extension ​

Add a visibility field to Markdown files:

yaml
---
type: concrete
ai-permission: read-write
visibility: [developer]         # New field
---

Visibility values: developer, management, dispatcher, stakeholder.

Files without a visibility field default to all roles (public within the org).


Phase 2: Knowledge Graph & Network View ​

Extend the context-engine from flat vector search into a relationship-aware knowledge graph, and surface it as an interactive network visualization in the docs-hub.

Document Relationship Graph ​

Build a graph of document relationships at index time:

Relationship TypeHow it's extractedExample
Cross-referencesParse markdown links between docsschema-commerce.md β†’ event-contracts-commerce.md
Shared entitiesNER extraction (table names, enum values, domain terms)BOOKING appears in 12 docs
Heading co-occurrenceSame heading text across files"State Machine" in 4 schemas
ADR impactADR files reference affected schemas/protocolsadr-006 β†’ kalkulations-engine.md
Frontmatter tagstype, visibility, domain contextAll type: concrete ops docs cluster together

The context-engine's indexer extracts these relationships during pnpm run index and stores them as edges alongside the embedding vectors.

Interactive Network Visualization ​

An Obsidian-like graph view embedded as a VitePress page (/graph):

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ“Š Documentation Network                   πŸ” Filter β–Ύ  β”‚
β”‚                                                            β”‚
β”‚        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                        β”‚
β”‚       β•±β”‚ schema-  β”‚β•²      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”‚
β”‚      β•± β”‚ commerce β”‚ ╲────▢│ event-       β”‚                 β”‚
β”‚     β•±  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β•²    β”‚ contracts-   β”‚                 β”‚
β”‚    β•±        β”‚         β•²   β”‚ commerce     β”‚                 β”‚
β”‚   β–Ό         β–Ό          β–Ό  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                 β”‚
β”‚ β”Œβ”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”                             β”‚
β”‚ β”‚adr β”‚  β”‚kalkul- β”‚  β”‚PRODUCT β”‚                             β”‚
β”‚ β”‚006 β”‚  β”‚ations  β”‚  β”‚paymentsβ”‚                             β”‚
β”‚ β””β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜                             β”‚
β”‚                                                            β”‚
β”‚  Nodes: 84  β”‚  Edges: 312  β”‚  Clusters: 6                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Technology choice: D3.js force-directed graph (or @antv/g6 for richer interaction). Renders in the browser as an interactive canvas β€” zoom, pan, node click navigates to the doc page.

FeatureDetail
Node = documentSized by number of connections ("god nodes" are visually larger)
Edge = relationshipColor-coded by type (cross-ref, shared entity, ADR impact)
ClustersAuto-detected via community detection (Leiden/Louvain), colored by domain
ClickNavigates to the doc page in the docs-hub
SearchType to highlight a node and its neighbors
FiltersToggle relationship types, filter by domain (Product, Architecture, Schemas)
Local graphEach doc page gets a mini-graph showing its direct neighbors (like Obsidian's local graph)

Graphify Integration ​

The existing Graphify integration (docs/architecture/ai.md) builds a code-level knowledge graph (call graphs, cross-module dependencies). Phase 2 connects both graphs:

LayerSourceGraph TypePurpose
Documentation graphcontext-engine indexerDoc-to-doc relationshipsNavigate documentation structure
Code graphGraphifyAST-extracted code relationshipsNavigate code architecture
Bridge edgesFrontmatter implements fieldDoc β†’ code, code β†’ docTrace from spec to implementation

The bridge layer requires adding an implements frontmatter field to protocol/schema docs:

yaml
---
type: concrete
implements:
  - apps/api/src/modules/commerce/booking.service.ts
  - apps/api/src/modules/commerce/payment.service.ts
---

This enables the network view to show: "This protocol spec is implemented by these code modules" β€” and vice versa when exploring code via Graphify.

Context-Engine Extensions ​

The context-engine package evolves to support graph-aware search:

CapabilityMVP (Vector-only)Phase 2 (Graph-enriched)
SearchCosine similarity on flat chunksGraph-walk neighbors to expand context
RetrievalTop-8 most similar chunksTop-8 + their graph neighbors (BFS depth 1)
System promptFlat context injectionContext + relationship context ("this doc references X, which says...")
IndexerChunks by headingChunks by heading + edge extraction

Migration Path from docs-assistant (Completed) ​

StepActionStatus
1Create studio/docs-hub/ with VitePress scaffoldβœ… Done
2Configure sidebar, search, brandingβœ… Done
3Build packages/context-engine/ standalone serverβœ… Done
4Move RAG logic from docs-assistant β†’ context-engineβœ… Done
5Implement chat drawer component in VitePress themeβœ… Done
6Add password auth gate + localStorage chat persistenceβœ… Done
7Deploy docs-hub (static) to Hetzner SwarmTodo
8Deploy context-engine server to Hetzner SwarmTodo
9Remove studio/docs-assistant/βœ… Done
10Phase 2: Knowledge graph extraction in indexerFuture
11Phase 2: Interactive network view (/graph page)Future
12Phase 2: Graphify bridge + graph-enriched RAGFuture

Internal documentation β€” Busflow