feat: merge Docusaurus and Gitea documentation skills
validate / validate (push) Successful in 6s

This commit is contained in:
2026-08-14 12:32:08 +00:00
parent cfb006d7ea
commit b08614dc90
28 changed files with 4202 additions and 1 deletions
+42
View File
@@ -0,0 +1,42 @@
# Architecture information-architecture remediation
Use this workflow when a Docusaurus architecture section builds successfully but does not satisfy its required navigation or content contract.
## Separate the three verdicts
Audit and report these independently:
1. **Structural compliance** — dedicated docs plugin/sidebar, exact ordering and nesting, stable document IDs, routes, and registry links.
2. **Content completeness** — required C4 levels, registries, responsibilities, relationships, traceability, and explicit placeholders for gated decisions.
3. **Build/render health** — structure validator, typecheck, strict broken-link production build, diagram-source validation, browser rendering, and console errors.
A green build proves only build health. An autogenerated sidebar or matching headings inside one page does not prove structural compliance.
## Safe remediation sequence
1. Fetch the remote base branch and create a clean worktree/branch from its exact SHA.
2. Encode the required hierarchy in a deterministic validator and run it once to prove RED.
3. Replace autogenerated navigation with an explicit sidebar when semantic order matters.
4. Add the smallest compliant pages and registries. If product/solution approval is absent, create visibly gated scaffolding rather than inventing requirements, decisions, tasks, interfaces, or versions.
5. Keep editable `.drawio` sources beside the consuming architecture pages. Render them through `docusaurus-plugin-drawio` and MDX raw imports; do not substitute download links or static exports.
6. Run the structure validator, Draw.io/XML validation, typecheck, strict-link production build, and `git diff --check`.
7. Serve the production build and verify the actual sidebar nesting, representative routes, rendered diagram DOM, and browser console.
8. Commit and push, open a reviewable PR, read back its head/base SHAs and changed-file list, and wait for the exact commit's CI status to reach a terminal state.
9. Post one concise evidence message in the governing project channel with PR, commit, structure, validation, and remaining human gate.
## Docusaurus link pitfall
Docusaurus may resolve a source-relative link such as `./containers` from the generated page route (`/architecture/overview/`) rather than from the source-file directory, producing `/architecture/overview/containers/`. For links between sibling architecture documents, prefer explicit site-root routes such as `/architecture/containers/` and `/architecture/features/<id>/overview/`, then let the production build's broken-link check verify them.
Do not weaken `onBrokenLinks` or broken-Markdown-link handling to make remediation pass.
## Verification evidence to retain
- base branch and SHA;
- feature branch and commit SHA;
- validator output;
- strict production-build result;
- Draw.io validation result for every edited source;
- browser evidence for sidebar hierarchy and representative rendered diagram;
- zero relevant console/JavaScript errors;
- PR URL, mergeability, exact remote head SHA, and terminal CI result.
@@ -0,0 +1,65 @@
# Behavioral acceptance for Docusaurus documentation portals
Use this gate when a request includes interactive behavior such as theme switching, section navigation, or local search. A successful build and Actions run prove compilation/publication only; they do not prove the requested behavior works.
## Requirement-to-proof matrix
Write the matrix before implementation and require one direct proof per request:
| Requirement | Source/config proof | Built-artifact proof | Browser proof |
|---|---|---|---|
| Light/dark mode | `disableSwitch: false`; separate light/dark tokens | both selectors present in compiled CSS | click toggle; verify `data-theme`, persisted theme, and computed colors all change |
| Dedicated section sidebars | separate docs-plugin instances, sidebar files, and `docSidebar` navbar items | each section route renders its own sidebar labels | open every top-menu section and inspect `Docs sidebar`; ensure unrelated section labels are absent |
| Local search | local-search dependency/theme configured; no hosted provider | generated search index contains representative terms from every docs instance | type a query, wait for loading to end, confirm a visible result, and open it |
Do not report completion until every requested row has a browser proof or an explicitly stated blocker.
## Multi-section sidebars
A differently named category inside one shared sidebar is not a dedicated sidebar. For top-level menu areas that must own navigation:
1. Keep one default docs instance for overview/global theme compatibility.
2. Add one `@docusaurus/plugin-content-docs` instance per section with a unique `id`, content `path`, `routeBasePath`, and `sidebarPath`.
3. Give each sidebar an independently named root, such as `projectsSidebar`.
4. Configure each navbar entry as `type: 'docSidebar'`, with both `docsPluginId` and `sidebarId`.
5. Give each section index `slug: /` relative to its plugin route.
6. Build and inspect every top-level route, not only the source configuration.
## Static local-search verification
Use `@easyops-cn/docusaurus-search-local` as a theme and include every docs route base in `docsRouteBasePath`. Search must remain static/browser-local, with no Algolia or hosted crawler credentials.
Prefer `hashed: 'filename'` on static hosting that redirects requests carrying query strings. With `hashed: true`, the theme fetches `search-index.json?_=HASH`; a host that redirects query-string asset requests can leave the search field indefinitely loading even though `search-index.json` exists. Filename hashing emits and fetches `search-index-HASH.json` directly.
Verification:
1. Assert exactly one `search-index*.json` exists in `build/`.
2. Inspect it for representative terms from overview and every docs plugin.
3. Serve the production build under the real configured `baseUrl`.
4. Type a representative query in the navbar.
5. Confirm loading ends and a visible result appears.
6. Open the result and verify its route.
7. After deployment, fetch the exact generated hashed index from the Pages backend and repeat term checks.
## Theme debugging
If the toggle exists but the appearance does not change, inspect hard-coded colors before changing Docusaurus configuration. Typical root causes are:
- `html, body` fixed to the dark background;
- navbar/sidebar/footer colors fixed with `!important`;
- custom homepage CSS modules defining only dark values;
- light and dark Prism themes configured identically.
Move component colors behind semantic variables. Define a complete light palette in `:root, [data-theme='light']` and dark overrides in `[data-theme='dark']`. For CSS modules, use `:global([data-theme='light']) .page` to override page-level tokens. Browser verification must compare computed colors, not only the `data-theme` attribute.
## Reporting discipline
Do not substitute these for behavioral acceptance:
- local TypeScript success;
- production-build success;
- successful CI;
- a deployed HTML marker;
- presence of the toggle/search input/sidebar container.
Those are supporting checks. Report the pipeline only after matching it to the intended commit SHA, and report the feature only after exercising it.
+90
View File
@@ -0,0 +1,90 @@
# C4 Architecture in Docusaurus — Mermaid Syntax Reference
Mermaid (built into Docusaurus via `@docusaurus/theme-mermaid`) supports C4 diagrams natively.
## L1 — C4Context
```mermaid
C4Context
title System Context — My Application
Person(user, "User", "Description of the human actor.")
System(mySystem, "My System", "What it does in one sentence.")
System_Ext(extA, "External System A", "Description")
System_Ext(extB, "External System B", "Description")
Rel(user, mySystem, "Uses")
Rel(mySystem, extA, "Fetches data", "REST API")
Rel(mySystem, extB, "Delivers output", "Webhook")
```
## L2 — C4Container
```mermaid
C4Container
title Container Diagram — My Application
Person(user, "User", "Description")
System_Boundary(mySystem, "My Application") {
Container(api, "API Service", "Python / FastAPI", "Handles requests.")
Container(worker, "Background Worker", "Python / Celery", "Async processing.")
ContainerDb(db, "Database", "PostgreSQL", "Stores data.")
Container(notifier, "Notifier", "Python", "Sends notifications.")
}
System_Ext(extA, "External System", "Description")
Rel(user, api, "Calls", "HTTPS")
Rel(api, worker, "Enqueues tasks", "Redis")
Rel(worker, db, "Reads/writes", "SQL")
Rel(notifier, extA, "Posts", "Webhook")
```
## L3 — C4Component
```mermaid
C4Component
title Components — API Service
Container_Ext(db, "Database", "PostgreSQL", "Stores data")
Container_Ext(worker, "Worker", "Celery", "Async tasks")
Container_Boundary(api, "API Service") {
Component(router, "Request Router", "FastAPI", "Routes HTTP requests.")
Component(authMiddleware, "Auth Middleware", "Python", "Validates tokens.")
Component(handler, "Request Handler", "Python", "Business logic.")
Component(repo, "Repository", "SQLAlchemy", "DB access layer.")
}
Rel(router, authMiddleware, "Passes request")
Rel(authMiddleware, handler, "Authenticated request")
Rel(handler, repo, "Queries data")
Rel(repo, db, "SQL", "TCP")
Rel(handler, worker, "Enqueues task", "Redis")
```
## Sidebar Structure for C4
```ts
// In sidebars.ts — recommended structure per app
{
type: 'category',
label: 'C4 Architecture',
collapsed: false,
items: [
'applications/app-name/c4/context', // L1
'applications/app-name/c4/containers', // L2
'applications/app-name/c4/components', // L3
],
},
```
## Tips
- Each C4 level is a separate `.md` file under `docs/applications/<app>/c4/`
- Add an `:::info C4 Model — Level N` admonition at the top of each page to explain what the level shows
- For interactive diagrams (richer layout), use `draw.io` via `docusaurus-plugin-drawio` instead of Mermaid
- Mermaid C4 is best for quick text-as-code diagrams; drawio is better for polished presentation diagrams
+218
View File
@@ -0,0 +1,218 @@
# Configuration Reference
Essential `docusaurus.config.ts` options. Not exhaustive—see [Docusaurus docs](https://docusaurus.io/docs/api/docusaurus-config) for everything.
---
## Minimum Viable Config
```typescript
const config = {
title: 'Project Name',
url: 'https://docs.example.com',
baseUrl: '/',
presets: [
['classic', {
docs: { routeBasePath: '/' }, // Docs at root
blog: false, // Disable if not using
theme: { customCss: './src/css/custom.css' },
}],
],
};
```
My opinion: Start minimal. Add complexity only when you need it.
---
## The Options That Actually Matter
### Site Identity
```typescript
title: 'Your Project', // Browser tab, search results
tagline: 'One sentence pitch', // Shows in default homepage
favicon: 'img/favicon.ico',
```
### URLs
```typescript
url: 'https://docs.example.com', // Production URL (no trailing slash)
baseUrl: '/', // Usually '/' unless in subdirectory
trailingSlash: false, // Pick one and stick with it
```
**Opinion:** `trailingSlash: false`. It's cleaner and most platforms handle it correctly.
### Build Behavior
```typescript
onBrokenLinks: 'throw', // Fail build on broken links
onBrokenMarkdownLinks: 'throw', // Same for markdown links
```
**Opinion:** Always `throw` in production. `warn` during development is OK.
---
## Preset: Classic (Use This)
```typescript
presets: [
['classic', {
docs: {
sidebarPath: './sidebars.ts',
routeBasePath: '/', // Docs as homepage
editUrl: 'https://github.com/org/repo/edit/main/',
showLastUpdateTime: true, // Shows git commit time
},
blog: {
showReadingTime: true,
blogSidebarCount: 'ALL',
},
theme: {
customCss: './src/css/custom.css',
},
}],
],
```
### Docs-Only Mode
Set `routeBasePath: '/'` and either `blog: false` or keep blog at `/blog`.
---
## Theme Config: What Readers See
### Navbar
```typescript
themeConfig: {
navbar: {
title: 'Project',
logo: { alt: 'Logo', src: 'img/logo.svg' },
items: [
{ type: 'docSidebar', sidebarId: 'docs', label: 'Docs' },
{ to: '/blog', label: 'Blog' },
{ href: 'https://github.com/...', label: 'GitHub', position: 'right' },
],
},
}
```
**Opinion:** Keep navbar items under 5. More than that, use dropdowns.
### Color Mode
```typescript
colorMode: {
defaultMode: 'light', // Or 'dark'
respectPrefersColorScheme: true, // Honor system preference
},
```
**Opinion:** Always `respectPrefersColorScheme: true`. Don't force your preference.
### Sidebar Behavior
```typescript
docs: {
sidebar: {
hideable: true, // Let users collapse it
autoCollapseCategories: true, // Only one category expanded
},
},
```
### Search (Algolia)
```typescript
algolia: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_SEARCH_API_KEY', // Public search-only key
indexName: 'YOUR_INDEX',
},
```
Apply at [Algolia DocSearch](https://docsearch.algolia.com/apply). It's free for open source.
---
## Plugins Worth Adding
### Client-Side Redirects
```typescript
plugins: [
['@docusaurus/plugin-client-redirects', {
redirects: [
{ from: '/old-page', to: '/new-page' },
],
}],
],
```
Use for moved pages. Don't leave 404s.
### Ideal Image
```typescript
['@docusaurus/plugin-ideal-image', {
quality: 85,
max: 1030,
min: 640,
}],
```
Auto-generates responsive images. Worth it for image-heavy docs.
---
## Things I Never Configure
These defaults are fine:
- `i18n` (unless you actually have translations ready)
- `onDuplicateRoutes` (the default `warn` is fine)
- `staticDirectories` (default `static` works)
- `titleDelimiter` (the `|` is universal)
Don't add config for things you're not using. It's clutter.
---
## Config Anti-Patterns
**Don't:** Copy entire example configs and modify
**Do:** Start minimal, add what you need
**Don't:** Set `onBrokenLinks: 'ignore'` to "fix" build errors
**Do:** Actually fix the broken links
**Don't:** Add `customFields` unless you're using them in code
**Do:** Keep config lean
---
## Environment Variables
For values that change between environments:
```typescript
const config = {
url: process.env.SITE_URL || 'http://localhost:3000',
customFields: {
apiUrl: process.env.API_URL,
},
};
```
Access in components:
```javascript
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
const { siteConfig } = useDocusaurusContext();
const apiUrl = siteConfig.customFields.apiUrl;
```
@@ -0,0 +1,63 @@
# Cross-linking source dossiers to generated derived dossiers
Use this pattern when canonical entity pages contain observations that are consolidated into generated records such as systems, suppliers, or projects.
## Objective
Add a generated column to each source observation table that displays the derived record's internal ID and links to its documentation route, without editing the canonical source corpus or duplicating identity logic.
Example:
```md
| Service or system | System dossier | Relationship | Evidence | Confidence |
|---|---|---|---|---|
| PPVIS | [`SYS-2D859CB9C5`](/org/repo/systems/ppvis-2d859cb9c5/) | operates | … | high |
```
## Generation pattern
1. Parse canonical source tables and retain a stable observation locator, normally `(source_file, source_line)`.
2. Run the canonical identity/consolidation algorithm once.
3. When assigning each derived record its ID and slug, annotate every contributing observation with both values.
4. Return an observation-link map from the derived-registry generator:
```python
{
(observation["source_file"], observation["source_line"]): {
"id": observation["derived_id"],
"slug": observation["derived_slug"],
}
}
```
5. Generate MDX-safe source dossier copies. Add the new table header and inject one link into each data row using that map.
6. Leave canonical source Markdown unchanged; generated display enrichment belongs in the publishing layer.
## Coverage invariant
Generation must fail when any parsed observation is not linked:
```python
expected = sum(1 for source, _line in observation_links if source == source_file)
if linked != expected:
raise SystemExit(
f"derived-link coverage mismatch in {source_file}: "
f"linked={linked} expected={expected}"
)
```
After generation, independently verify:
- displayed link count equals the registry observation count;
- every `(ID, slug)` target exists in the generated registry;
- the number of unique targets equals the expected derived-record count;
- representative legacy and structured table formats render correctly;
- the full Docusaurus production build and route validator pass.
## Important pitfalls
- Do not recompute identity independently in the source-page generator. Reuse the canonical registry result or links will drift when aliases change.
- Do not match only by displayed label; duplicate labels and client-scoped generic resources make that unsafe.
- Do not use the internal ID as a guessed route. Display the ID, but link with the generated canonical slug.
- Preserve numeric source line locators until enrichment is complete. Transformations such as frontmatter injection or `<br>` normalization can otherwise shift line numbers.
- Ensure generated internal links are not collected into public-source References indexes; references should remain provenance links, not site-navigation links.
@@ -0,0 +1,151 @@
# Dedicated Docusaurus Homepage + Native Local Search
Use this pattern when a docs-only Docusaurus site needs a product/showcase homepage and search that runs entirely from static build artifacts.
## Route separation
A docs plugin with `routeBasePath: '/'` can coexist with `src/pages/index.tsx`, but no doc may also claim `/`.
```md
---
id: intro
title: Documentation overview
slug: /overview
---
```
After moving the intro, re-check Markdown links. A link such as `./methodology` from `/overview/` resolves under `/overview/`; use `../methodology`, a Docusaurus doc link, or an explicit base-path URL.
Keep footer/navbar links explicit:
```ts
{label: 'Home', to: '/'},
{label: 'Overview', to: '/overview'},
```
## Local search without hosted services
Install at the root of a root-only pnpm workspace:
```bash
pnpm add -w @easyops-cn/docusaurus-search-local
```
Configure it as a **theme**, not a plugin:
```ts
themes: [
[
require.resolve('@easyops-cn/docusaurus-search-local'),
{
hashed: true,
indexDocs: true,
indexBlog: false,
indexPages: true,
docsRouteBasePath: ['/', 'clients'],
language: ['en'],
highlightSearchTermsOnTargetPage: true,
explicitSearchResultPath: true,
searchResultLimits: 12,
searchResultContextMaxLength: 120,
},
],
],
```
`docsRouteBasePath` must cover every docs plugin whose content should be searchable. Do not add Algolia credentials, crawler configuration, Ask AI, or another network service when the requirement is browser-local search.
### One SearchBar only
The theme supplies the navbar `@theme/SearchBar`. Avoid importing and rendering another `SearchBar` on the homepage: duplicate instances can contend for the singleton autocomplete/index lifecycle and leave the UI with input text but no fetched index or result overlay.
Use a landing-page trigger instead:
```tsx
const openSearch = () => {
const input = document.querySelector<HTMLInputElement>('.navbar__search-input');
if (input) {
input.focus();
return;
}
document.dispatchEvent(
new KeyboardEvent('keydown', {key: 'k', ctrlKey: true, bubbles: true}),
);
};
<button type="button" onClick={openSearch}>
Search documentation <kbd>Ctrl/⌘ + K</kbd>
</button>
```
Keep the navbar field itself as the sole search implementation.
## Generated homepage metrics
Do not hard-code changing registry statistics into JSX. Extend the canonical content generator to emit an ignored TypeScript module:
```python
summary = ROOT / "src" / "generated" / "registrySummary.ts"
summary.parent.mkdir(parents=True, exist_ok=True)
summary.write_text(
"export const registrySummary = "
+ json.dumps({"total": total, "researched": researched}, separators=(",", ":"))
+ " as const;\n",
encoding="utf-8",
)
```
Add `/src/generated` to `.gitignore`, run generation before both `tsc` and `docusaurus build`, and import from `@site/src/generated/registrySummary`.
## Government-portal visual adaptation
When adapting an official public-sector site, copy the visual grammar rather than cloning its page:
- derive a small token set from the real site (navy, action blue, pale neutral background, white surfaces, restrained borders);
- use a locally installed font package to avoid runtime font dependencies;
- reuse only appropriate official marks with clear provenance;
- reproduce hierarchy: narrow institutional strip, white utility navigation, large editorial hero, evidence/status blocks, and dark institutional footer;
- preserve the user's established geometry preference (for Lego, globally force `border-radius: 0` even if the source site uses pills);
- provide dark-mode equivalents without replacing the light-first official character.
For the VSSA pattern, the useful observed tokens were approximately navy `#091a5a`, action blue `#0b66e4`, pale background `#f3f6f9`, and Public Sans. The official coat-of-arms SVG was suitable as a local navbar asset.
## Public page unchanged: distinguish an untriggered workflow from a failed workflow
When a user cannot see locally completed changes, do not begin with runner debugging. Establish the publication chain in order:
1. `git status --short --branch` — are the changes still uncommitted?
2. `git rev-list --left-right --count HEAD...origin/main` — were they pushed?
3. Read the newest Actions run and compare its `head_sha` to the intended commit.
4. If the newest run still points to the previous SHA, Actions did not fail; the new workflow was never triggered. Commit and push.
5. Wait for the run attached to the intended SHA to reach `completed success`.
6. Fetch the public page with `Cache-Control: no-cache` and a query marker such as `?verify=<short-sha>`; assert a distinctive new heading or asset, not only HTTP 200.
7. Verify the public `search-index.json` contains representative searchable terms, then run a real browser query and open a result.
This sequence prevents two common false diagnoses: treating a successful local build as a deployment, and treating an old successful Actions run as evidence that a new change was published.
## Verification gate
Run all of these before declaring completion:
```bash
pnpm validate
pnpm build
test -s build/search-index.json
```
Then programmatically inspect the index for representative terms from:
1. the main docs plugin;
2. every secondary docs plugin;
3. the custom homepage when `indexPages: true`.
Finally, serve the production build and verify in a browser:
- desktop and mobile homepage layout;
- navbar search and keyboard shortcut;
- a query produces visible results;
- opening a result reaches the expected dossier route;
- square geometry and responsive navigation remain intact.
Publication is complete only after commit/push, a successful Gitea Actions run, and HTTP/content verification of the public homepage, overview, representative result route, and search index.
+216
View File
@@ -0,0 +1,216 @@
# Deployment
Getting your docs live. Pick the platform that fits your workflow.
---
## Quick Decision
| Platform | Best For | Effort |
|----------|----------|--------|
| **Vercel** | Teams already using Vercel | Lowest |
| **Netlify** | Need redirects, forms, functions | Low |
| **GitHub Pages** | Open source, simple hosting | Low |
| **Cloudflare Pages** | Global performance, free tier | Low |
**My recommendation:** Vercel or Netlify for most projects. GitHub Pages if you want everything in one repo.
---
## Vercel
### Setup
1. Push to GitHub
2. Import at vercel.com
3. Done—it auto-detects Docusaurus
### Config (vercel.json, usually not needed)
```json
{
"buildCommand": "npm run build",
"outputDirectory": "build"
}
```
Vercel handles this automatically. Only add if you need overrides.
---
## Netlify
### Setup
1. Push to GitHub
2. Connect at app.netlify.com
3. Build command: `npm run build`
4. Publish directory: `build`
### Config (netlify.toml)
```toml
[build]
command = "npm run build"
publish = "build"
[[redirects]]
from = "/old-path"
to = "/new-path"
status = 301
```
**Opinion:** Netlify's redirect handling is cleaner than most.
---
## GitHub Pages
### Config
```typescript
// docusaurus.config.ts
const config = {
url: 'https://username.github.io',
baseUrl: '/repo-name/', // or '/' for username.github.io
organizationName: 'username',
projectName: 'repo-name',
trailingSlash: false,
};
```
### GitHub Actions (recommended)
`.github/workflows/deploy.yml`:
```yaml
name: Deploy
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run build
- uses: actions/upload-pages-artifact@v3
with:
path: build
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/deploy-pages@v4
id: deployment
```
Enable Pages in repo settings → Pages → Source: GitHub Actions.
---
## Cloudflare Pages
### Setup
1. Connect repo at pages.cloudflare.com
2. Framework: None (not auto-detected well)
3. Build command: `npm run build`
4. Output directory: `build`
### Why Cloudflare
- Fastest global CDN
- Generous free tier
- Web analytics included
---
## Pre-Deployment Checklist
Before pushing:
```bash
# 1. Build locally first
npm run build
# 2. Test the build
npm run serve
# 3. Check for broken links (build will fail if onBrokenLinks: 'throw')
# 4. Verify all images load
# 5. Test search works (if Algolia configured)
```
---
## Custom Domain
All platforms support custom domains. General steps:
1. Add domain in platform settings
2. Update DNS:
- `A` record to platform IP, or
- `CNAME` to platform URL
3. Wait for SSL certificate (automatic, usually minutes)
4. Update `url` in docusaurus.config.ts
**DNS propagation:** Can take up to 48 hours. Usually much faster.
---
## Environment-Specific Builds
For staging vs production:
```typescript
const isProd = process.env.NODE_ENV === 'production';
const config = {
url: isProd
? 'https://docs.example.com'
: 'https://staging-docs.example.com',
};
```
Or use platform environment variables.
---
## Common Issues
### Build Fails Locally But Worked Before
```bash
rm -rf node_modules .docusaurus
npm ci
npm run build
```
### Build Works Locally, Fails in CI
- Check Node.js version matches
- Check for case-sensitive filename issues (Linux is case-sensitive, macOS isn't)
- Check for absolute paths that only exist on your machine
### "Page Not Found" After Deploy
- Check `baseUrl` matches your hosting path
- Check `trailingSlash` is consistent
- Clear CDN cache if platform supports it
+241
View File
@@ -0,0 +1,241 @@
# Diátaxis Patterns for Docusaurus
Templates and patterns for each documentation quadrant.
---
## Tutorial Template
**Purpose:** Take someone from zero to "I built something!"
**Filename:** `getting-started/your-first-[thing].md`
```markdown
---
title: "Build Your First [Thing]"
description: "Create a working [thing] in 10 minutes"
---
# Build Your First [Thing]
By the end of this tutorial, you'll have a working [thing] that [does X].
## What You'll Build
[Screenshot or diagram of the end result]
## Prerequisites
- [Specific version] of [tool] installed
- Basic familiarity with [concept] (see [link] if new)
## Step 1: [Action verb] the [thing]
[One action, one visible result]
You should see:
```
[Expected output]
```
## Step 2: [Next action]
[Continue pattern...]
## What You've Built
You now have a [thing] that [capability].
**Next steps:**
- [Link to how-to guide for customization]
- [Link to explanation of how it works]
```
**Tutorial Anti-patterns:**
- "First, let's understand how X works..." → Link instead
- "You can also do Y..." → One path only
- "This is simple..." → Never
---
## How-To Guide Template
**Purpose:** Help someone accomplish a specific task.
**Filename:** `guides/how-to-[verb]-[thing].md`
```markdown
---
title: "How to [Verb] [Thing]"
description: "[Outcome] in [context]"
---
# How to [Verb] [Thing]
[One sentence: what this accomplishes]
## What You'll Need
- [Prerequisite 1]
- [Prerequisite 2]
## Steps
### 1. [Action]
```bash
command here
```
### 2. [Action]
[Instructions...]
## Verification
[How to confirm it worked]
## Troubleshooting
**[Symptom]:** [Quick fix or link to explanation]
```
**How-to Anti-patterns:**
- Teaching concepts (that's a tutorial)
- Listing all options (that's reference)
- Explaining why (that's explanation)
---
## Reference Template
**Purpose:** Describe what something IS, not how to use it.
**Filename:** `reference/[category]/[item].md`
```markdown
---
title: "[Component/API/Config] Reference"
description: "Complete reference for [thing]"
---
# [Thing] Reference
[One sentence: what this is]
## Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `name` | `string` | required | Brief description |
| `enabled` | `boolean` | `true` | Brief description |
## Examples
```javascript
// Minimal
{ name: "example" }
// Full
{
name: "example",
enabled: true,
// ...
}
```
## Related
- [Link to how-to guide using this]
- [Link to explanation of design]
```
**Reference Anti-patterns:**
- "To use this, first..." → That's a how-to
- "This is useful when..." → That's explanation
- Incomplete tables (every prop documented)
---
## Explanation Template
**Purpose:** Help someone understand WHY.
**Filename:** `concepts/[topic].md`
```markdown
---
title: "Understanding [Concept]"
description: "Why [thing] works the way it does"
---
# Understanding [Concept]
[Hook: why this matters to the reader]
## The Problem
[What challenge does this solve?]
## How [Product] Approaches This
[Your design decision and reasoning]
## Trade-offs
| Approach | Pros | Cons |
|----------|------|------|
| [Option A] | ... | ... |
| [Option B] | ... | ... |
**We chose [X] because [reasoning].**
## When to Choose Differently
[Acknowledge alternatives have merit in certain contexts]
## Further Reading
- [External resource]
- [Related concept in these docs]
```
**Explanation Anti-patterns:**
- Step-by-step instructions (how-to)
- Property tables (reference)
- "Let's build..." (tutorial)
---
## Deciding Which Type
Ask yourself:
```
Is the reader trying to LEARN?
├── Yes → Tutorial
└── No
└── Are they trying to DO something specific?
├── Yes → How-to Guide
└── No
└── Are they trying to LOOK UP information?
├── Yes → Reference
└── No → Explanation
```
**If you're unsure:** Write it as a how-to first. They're the most commonly needed and easiest to split later.
---
## Connecting the Quadrants
Good docs link between types:
- **Tutorial** → "To learn more about why this works, see [Explanation]"
- **Tutorial** → "For the full API, see [Reference]"
- **How-to** → "Prerequisites: complete [Tutorial] first"
- **How-to** → "For all options, see [Reference]"
- **Reference** → "For a walkthrough, see [How-to]"
- **Explanation** → "To try this yourself, see [Tutorial]"
Never dead-end a reader. Always point them forward.
+128
View File
@@ -0,0 +1,128 @@
# Docusaurus Config Template — Modern Dark Theme
Full `docusaurus.config.ts` for a modern dark-first docs site with:
- draw.io plugin (`docusaurus-plugin-drawio`)
- Mermaid C4 diagrams (`@docusaurus/theme-mermaid`)
- Dark mode default
- Prism oneDark theme
- Announcement bar
- Sidebar TOC config
```ts
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
const config: Config = {
title: 'My Project',
tagline: 'Documentation tagline',
favicon: 'img/favicon.ico',
url: 'https://docs.example.com',
baseUrl: '/',
organizationName: 'org-name',
projectName: 'documentation',
onBrokenLinks: 'throw',
markdown: {
hooks: {
onBrokenMarkdownLinks: 'warn',
},
mermaid: true,
},
i18n: { defaultLocale: 'en', locales: ['en'] },
themes: ['@docusaurus/theme-mermaid'],
plugins: ['docusaurus-plugin-drawio'],
presets: [
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
editUrl: 'https://gitea.lego-cloud.eu/ORG/REPO/src/branch/main/',
showLastUpdateTime: true,
showLastUpdateAuthor: true,
},
blog: {
showReadingTime: true,
blogTitle: 'Changelog',
},
theme: {
customCss: './src/css/custom.css',
},
} satisfies Preset.Options,
],
],
themeConfig: {
colorMode: {
defaultMode: 'dark',
disableSwitch: false,
respectPrefersColorScheme: true,
},
announcementBar: {
id: 'wip',
content: '🚧 Under active development.',
backgroundColor: '#1a1a2e',
textColor: '#818cf8',
isCloseable: true,
},
navbar: {
title: 'My Project',
hideOnScroll: true,
items: [
{
type: 'docSidebar',
sidebarId: 'docsSidebar',
position: 'left',
label: 'Docs',
},
{
href: 'https://gitea.lego-cloud.eu/ORG',
position: 'right',
label: 'Gitea ↗',
},
],
},
footer: {
style: 'dark',
copyright: `© ${new Date().getFullYear()} My Project · Built by Jarvis`,
},
prism: {
theme: prismThemes.oneDark,
darkTheme: prismThemes.oneDark,
additionalLanguages: ['bash', 'python', 'sql', 'yaml', 'json', 'typescript', 'docker'],
},
mermaid: {
theme: { light: 'neutral', dark: 'dark' },
},
tableOfContents: {
minHeadingLevel: 2,
maxHeadingLevel: 4,
},
} satisfies Preset.ThemeConfig,
};
export default config;
```
## Required packages
```bash
npx pnpm add @docusaurus/theme-mermaid docusaurus-plugin-drawio raw-loader
```
## Pitfalls
- `onBrokenMarkdownLinks` moved to `markdown.hooks.onBrokenMarkdownLinks` in Docusaurus v3 — using `siteConfig.onBrokenMarkdownLinks` triggers a deprecation warning
- Remove blog from navbar if you have no blog posts — broken link on build
- `to: 'https://...'` does NOT work for external links in navbar; use `href:` instead
+146
View File
@@ -0,0 +1,146 @@
# Draw.io Source Diagrams in Docusaurus
Use this workflow when a Docusaurus v3 site must render an editable Draw.io source file directly. The integration is based on [`xiguaxigua/docusaurus-plugin-drawio`](https://github.com/xiguaxigua/docusaurus-plugin-drawio) and its upstream README.
## Source-of-truth rule
- Commit the editable `.drawio` XML in the documentation repository.
- Render that source directly in MDX; do not make a PNG/SVG export the only authoritative artifact.
- Keep the source adjacent to, or in a clearly named diagram directory near, the page that consumes it.
- Use the global `drawio-main` skill to author and validate diagram content.
- Use this `documentation-docusaurus` skill to onboard and render the source in Docusaurus.
Recommended layout:
```text
docs/
└── architecture/
├── context.mdx
└── diagrams/
└── system-context.drawio
```
## Install
This platform uses pnpm. Add the plugin and the raw loader required by the upstream import syntax:
```bash
pnpm add docusaurus-plugin-drawio
pnpm add -D raw-loader
```
Commit `package.json` and `pnpm-lock.yaml`. Do not substitute npm or create a second lockfile.
## Register the plugin
Add the plugin to `docusaurus.config.ts` or `docusaurus.config.js`:
```ts
export default {
// ...
plugins: [
['drawio', {}],
],
};
```
Preserve existing plugins. Do not replace the whole array merely to add Draw.io.
The plugin uses this viewer script by default:
```text
https://cdn.jsdelivr.net/npm/docusaurus-plugin-drawio/viewer.min.js
```
For production environments that require controlled or offline assets, host `viewer.min.js` from an approved trusted location and configure it explicitly:
```ts
plugins: [
['drawio', {lib: '/drawio/viewer.min.js'}],
],
```
Verify the configured path is published and reachable. Do not point production documentation at an unapproved HTTP origin.
## Consume a `.drawio` source file from MDX
The page must use `.mdx`, because it imports and renders a React component:
```mdx
---
title: "Architecture context"
description: "System context and external relationships."
---
import Drawio from '@theme/Drawio';
import systemContext from '!!raw-loader!./diagrams/system-context.drawio';
<Drawio
content={systemContext}
title="System context"
toolbar="zoom layers lightbox"
responsive
/>
```
The `!!raw-loader!` prefix loads the Draw.io XML as text. The `Drawio` theme component passes that XML to the viewer.
For a multi-page source file, select a page by zero-based/numeric page index as supported upstream:
```mdx
<Drawio content={architecture} page={1} />
```
Or select a stable page ID when the source uses one:
```mdx
<Drawio content={architecture} pageId="containers" />
```
Prefer one source file per durable diagram unless a multi-page source has a clear ownership reason. If using multiple pages, document page names/IDs and verify every consumed page after edits.
## Supported display controls
The upstream plugin exposes Draw.io viewer properties including `page`, `pageId`, `toolbar`, `zoom`, `maxHeight`, `title`, `responsive`, `layers`, `lightbox`, and navigation/resize controls. Use only properties verified by the site build and browser rendering. Do not copy undocumented combinations without testing.
## Validation
Run the repository's prescribed checks, at minimum:
```bash
pnpm install --frozen-lockfile
pnpm build
```
Then serve or preview the built site and verify in a browser:
1. the page loads without console errors;
2. the diagram is visible and readable;
3. zoom/lightbox controls work when enabled;
4. the selected page/pageId is correct;
5. diagram labels and relationships match the `.drawio` source;
6. the browser can load `viewer.min.js` from the configured location;
7. no mixed-content, CSP, missing-module, or raw-loader error appears.
Also verify that every MDX import resolves to an existing committed `.drawio` file. A successful XML edit without a successful Docusaurus build and browser render is incomplete.
## Common failures
- **`Module not found: raw-loader`** — add `raw-loader` as a development dependency and refresh the pnpm lockfile.
- **MDX parse/import failure** — use `.mdx`, keep imports after frontmatter, and check the relative source path.
- **Blank viewer** — inspect browser console/network output, validate the Draw.io XML, and verify `viewer.min.js` is reachable.
- **Wrong page shown** — verify `page` or `pageId` against the current source; page ordering can drift.
- **Works in development but not production** — check production base URL, CSP, static asset path, and viewer CDN/network policy.
- **Stale exported image** — remove the export from the authority path or regenerate it; the `.drawio` source remains authoritative.
## Evidence to record
Record exact paths for:
- `.drawio` source;
- consuming `.mdx` page;
- Docusaurus configuration;
- `package.json` and `pnpm-lock.yaml` changes;
- build result;
- browser/console verification;
- branch and commit SHA.
@@ -0,0 +1,40 @@
# Generated evidence registries in Docusaurus
Use this pattern when a canonical Markdown corpus must also expose a cross-client registry such as systems, products, suppliers, or services.
## Preserve the canonical layer
- Keep client/source Markdown and its validator authoritative.
- Generate ignored MDX copies and registry pages at build time.
- Never edit generated pages to improve evidence; improve canonical table rows and regenerate.
- Emit a machine-readable registry JSON alongside human-readable pages.
## Conservative identity model
1. Parse only the canonical, schema-defined table under the expected heading.
2. Preserve every source observation with client ID, client route, relationship, meaning/context, contractor, evidence, and confidence.
3. Normalize case, whitespace, dashes, and Markdown decoration for matching only; preserve observed labels for display.
4. Maintain explicit reviewed alias rules in durable source data. Merge only unmistakable product/shared-service identities.
5. Keep composite labels separate unless evidence proves their components and relationships.
6. Scope generic labels such as “official website”, “institution portal”, or “virtual exhibition” to the client; identical wording across clients does not prove one shared system.
7. Use stable hash-derived registry IDs/slugs so later insertions do not renumber established records.
## Docusaurus integration
- Use a separate `@docusaurus/plugin-content-docs` instance with its own `id`, generated path, route base, and sidebar file.
- Include every docs-plugin route base in local-search configuration.
- A flat sidebar can be produced by generating all docs into one directory and using `{type: 'autogenerated', dirName: '.'}`; range directories create folder categories.
- Keep existing client slugs explicit when changing generated-directory layout.
- Validate source rows, unique records, generated routes, observation totals, representative pages, JSON output, and search-index terms after the real production build.
## Deployment verification behind authentication
A successful Actions run is not sufficient. First prove `HEAD == origin/main` and that the run's `head_sha` matches. Then verify deployment.
For an authentication-protected Pages ingress:
- unauthenticated public `302` to the configured OAuth start path is expected, not a publish failure;
- do not weaken or bypass authentication for users;
- verify content through an authorized browser session or the internal static-site service/backend when operational access permits it;
- check the homepage, one canonical dossier, registry index, one generated registry dossier, machine-readable JSON, and search index;
- distinguish ingress/authentication changes from static publication failures before rolling back application code.
+625
View File
@@ -0,0 +1,625 @@
# Gitea and Docusaurus Delivery
Set up Gitea organizations, repositories, and Docusaurus documentation sites with pnpm and Gitea Actions CI/CD pipelines.
## Trigger Conditions
- User asks to create a Gitea organization or repo
- User wants a documentation site (Docusaurus)
- User needs CI/CD for docs on Gitea
- Any project scaffolding on gitea.lego-cloud.eu
- User asks about Gitea Pages / static hosting for docs
- User wants to convert an established Markdown, evidence, registry, or research repository into Docusaurus without losing canonical paths or provenance
## Prerequisites
- Gitea API token from Bitwarden Secrets Manager as `HL_V1_GITEA_ACCESS_TOKEN`, injected into the Hermes gateway environment at startup
- Never use `GITHUB_TOKEN` for `gitea.lego-cloud.eu`; if the Gitea variable is absent, restart the gateway rather than copying it into `/opt/data/.env`
- SSH key configured for `ssh://git@gitea.lego-cloud.eu:30009/`
- Node.js 18+ available
- pnpm available (via `npx pnpm` if not globally installed)
## 1. Gitea API Operations
### Create Organization
```bash
: "${HL_V1_GITEA_ACCESS_TOKEN:?restart the Hermes gateway to load the Bitwarden Gitea token}"
curl -s -X POST "https://gitea.lego-cloud.eu/api/v1/orgs" \
-H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "org-name",
"full_name": "Display Name",
"description": "Description",
"visibility": "public"
}'
```
### Create Repository in Organization
```bash
curl -s -X POST "https://gitea.lego-cloud.eu/api/v1/orgs/ORG_NAME/repos" \
-H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "repo-name",
"description": "Description",
"private": false,
"auto_init": true,
"default_branch": "main"
}'
```
### Clone via SSH
```bash
git clone ssh://git@gitea.lego-cloud.eu:30009/ORG/REPO.git
cd REPO
git config user.email "jarvis.at.skic@gmail.com"
git config user.name "Jarvis"
```
## 2. Docusaurus Setup with pnpm
### Pitfalls
1. **pnpm not globally available**: Use `npx pnpm` as wrapper — corepack may fail with EACCES on permission-restricted systems.
2. **`create docusaurus` fails on non-empty dirs**: The `.git` folder counts. Create in a temp dir first, then copy files into the git repo.
3. **Interactive prompt blocks**: Always use `--typescript` flag to skip the language selection prompt.
4. **`pnpm install` fails first time**: Run `npx pnpm approve-builds @swc/core core-js` to approve post-install scripts, then retry the build.
5. **Broken link on build — `/blog`**: Docusaurus throws on build if the navbar links to `/blog` but no blog posts exist. Remove the blog nav item until posts exist.
6. **External links in navbar**: Use `href:` not `to:` for external URLs (GitHub, Gitea). `to:` triggers internal routing and breaks.
7. **`onBrokenMarkdownLinks` deprecation**: In Docusaurus v3, move this option to `markdown.hooks.onBrokenMarkdownLinks` — the top-level `siteConfig` version shows a deprecation warning.
8. **draw.io files must be co-located**: `.drawio` files imported via `!!raw-loader!` must be in the same `docs/` subtree as the `.mdx` importing them. Placing them in `/static/` and importing by path does NOT work.
9. **C4 Mermaid diagrams need the theme package**: `C4Context` / `C4Container` / `C4Component` require `@docusaurus/theme-mermaid` to be installed AND listed in `themes:` in config AND `markdown.mermaid: true`.
10. **Root-only pnpm workspace still needs `packages`**: If `pnpm-workspace.yaml` exists, include `packages: ['.']` (YAML list form is preferred). Otherwise pnpm 9 fails in CI with `packages field missing or empty` before Docusaurus builds.
11. **Runner-owned build means runner-owned verification**: When the user requires Gitea Actions to install/build, push the minimal change and inspect the actual Actions run rather than installing dependencies locally. Retrieve the failed job log, fix the observed step, and re-trigger until the runner and public URL both verify.
12. **Do not rewrite a canonical corpus to fit Docusaurus**: For established research/evidence repositories, keep canonical paths and validators intact. Generate ignored MDX-safe copies, add explicit slugs for stable numeric routes, publish byte-identical raw artifacts with checksums, and validate route/artifact counts after the real build. Follow `references/migrating-existing-markdown-corpora.md`.
13. **A custom homepage conflicts with a docs page at `/`**: Before adding `src/pages/index.tsx`, move the docs intro from `slug: /` to a stable route such as `slug: /overview`. Update relative Markdown links from the new route and keep the docs sidebar pointed at the same document ID.
14. **Local search should have one theme-owned search instance**: Use `@easyops-cn/docusaurus-search-local` as a Docusaurus theme and let the navbar render `@theme/SearchBar`. Do not render a second `SearchBar` on the landing page; multiple instances can interfere with lazy index loading and result overlays. A landing-page search control should focus the navbar input or dispatch the configured keyboard shortcut instead.
15. **Adding packages at a root-only pnpm workspace requires workspace intent**: use `pnpm add -w PACKAGE` when `pnpm-workspace.yaml` declares `packages: ['.']`; otherwise pnpm rejects the add as an accidental workspace-root mutation.
16. **An unchanged public page may mean Actions never triggered—not that Actions failed**: first compare local `git status`, `HEAD`, `origin/main`, and the newest Actions run `head_sha`. If the implementation is still uncommitted or unpushed and the latest run points to the previous commit, commit/push before debugging the runner. After success, verify a distinctive new content marker with a cache-busting query rather than relying on a possibly cached browser tab.
17. **Authentication can change the public verification result without breaking publication**: if Pages starts returning `302` to an OAuth start path, check another hosted site and the ingress/auth rollout before blaming the new Docusaurus build. Treat the configured auth redirect as expected, then verify content through an authorized browser session or the internal Pages backend. Do not change authentication to make a health check pass.
18. **Verify a protected Pages deployment through the Kubernetes service proxy when appropriate**: from an authorized MicroK8s control-plane node, `microk8s kubectl get --raw '/api/v1/namespaces/NAMESPACE/services/http:SERVICE:PORT/proxy/ORG/REPO/ROUTE/?verify=COMMIT'` exercises the deployed service without depending on host-side cluster DNS or embedding a raw ClusterIP. Derive `ROUTE` from the built output or generated registry slug—not from an entity ID, system ID, or guessed short path—and preserve the route's trailing slash when the static-site build emits directory indexes. A Kubernetes `NotFound` on a guessed route is a route-discovery failure, not evidence that publication failed. Check the response body for distinctive markers from changed pages, not merely byte count or HTTP success. This is an operator-side deployment check, not a way to bypass authorization for users.
19. **Exact text deduplication is unsafe for cross-client registries**: generic labels such as “official website” or “institution portal” can describe different resources. Scope generic labels to the client, preserve every source observation, and merge only reviewed aliases for unmistakably identical named systems. Follow `references/generated-evidence-registries.md`.
20. **Large evidence portals should be organized around user tasks, not docs-plugin internals**: make entity browsing, derived-record exploration, and evidence search the primary discovery paths; keep methodology and provenance globally accessible but secondary. Link global navigation to registry/index routes rather than arbitrary first dossiers, distinguish entity/system/observation counts, and generate changing overview content from canonical data. Follow `references/research-led-evidence-registry-ia.md`.
21. **Changing only `navbar.title` leaves inconsistent page titles**: update the top-level Docusaurus `title` as well as the visible navbar label, then verify `document.title` on the homepage and a docs route.
22. **Multiple top-level user tasks should not share one giant sidebar**: use a dedicated docs-plugin instance and sidebar for each major area. Keep one instance as the default plugin (omit `id`) because global search/404 rendering may call default docs hooks; disabling the default docs plugin without replacing it can pass compilation but fail static rendering. Follow `references/multi-section-evidence-portals.md`.
23. **Evidence tables need both readable presentation and source indexing**: for dense cross-entity relationships, generate reusable React evidence cards while preserving canonical observations and claim-level links. Add a de-duplicated References index to every generated entity and derived-record page, and document hash-derived IDs as internal rather than public identifiers. Follow `references/multi-section-evidence-portals.md`.
24. **A dedicated section can still have an unusably flat sidebar**: for large entity sections, make the first level exactly **Overview** plus an expandable **Registry** category; place every ordered entity dossier under Registry. Do not leave hundreds of dossiers beside Overview at the root. When constructing sidebar document IDs from generated filenames, verify Docusaurus's numeric-prefix behavior: it may strip a leading position prefix, but preserve the filename ID when the remaining basename itself begins with a digit. Prefer explicit frontmatter `id` values for new generators; otherwise derive IDs deliberately and prove the first numeric-leading and ordinary records in a production build. Follow `references/multi-section-evidence-portals.md`.
25. **Source observations should navigate to their generated derived dossiers**: when client/entity tables feed a generated system or supplier registry, enrich only the generated source-page copies with a dedicated dossier-link column. Display the internal derived ID as the anchor and link with the registry's canonical slug. Reuse the canonical identity result through a stable observation map such as `(source_file, source_line)`; do not recompute identity or match by label in the page generator. Fail generation unless every observation is linked, then verify link count, target validity, and unique-target coverage. Follow `references/cross-linking-source-and-derived-dossiers.md`.
26. **Reference shortlists must preserve novelty across turns**: track every previously suggested site. When the user asks for additional or “new” options, retain only explicitly selected references and replace all other entries; do not repeat rejected or already-presented choices.
27. **Inspect the exact selected reference URL before implementation**: similarly named documentation and wiki domains can have materially different entry pages. Extract hierarchy and visual principles from the user-selected URL, then create an original adaptation rather than relying on memory or cloning branded content.
28. **A reference site does not override explicit shape constraints**: if the chosen design uses rounded cards but the user requires sharp rectangles, preserve the layout and information hierarchy while setting all radius tokens to zero and, when the rule is global, applying `*, *::before, *::after { border-radius: 0 !important; }`. Verify the compiled production CSS contains the rule. See `references/reference-inspired-documentation-portals.md`.
29. **A green pipeline is not feature acceptance**: before implementation, map each interactive requirement to a direct browser-level proof. A build, successful Actions run, deployed marker, visible toggle, sidebar container, or search input proves infrastructure/presence—not behavior. For theme switching, verify `data-theme`, persistence, and computed colors; for dedicated sidebars, open every top-menu section and verify only its sidebar items; for local search, type a query, wait for loading to finish, confirm a visible result, and open it. Follow `references/behavioral-acceptance-for-doc-portals.md`.
30. **Local-search query hashing can conflict with static hosting redirects**: `hashed: true` fetches `search-index.json?_=HASH`. If the static host redirects asset requests with query strings, search can remain indefinitely loading despite a valid index. Use `hashed: 'filename'`, verify the emitted `search-index-HASH.json`, and exercise a real browser query under the configured `baseUrl`. Follow `references/behavioral-acceptance-for-doc-portals.md`.
31. **A named category is not a dedicated sidebar**: when top-menu sections require their own sidebars, use separate docs-plugin instances with unique IDs, paths, route bases, sidebar files, and navbar `docSidebar` items carrying both `docsPluginId` and `sidebarId`. Keep a default docs instance for overview/global compatibility, then inspect every rendered section sidebar. Follow `references/behavioral-acceptance-for-doc-portals.md`.
32. **Renaming a Pages-backed Gitea repository is also a deployment-path migration**: the publication workflow derives its destination from `${{ gitea.repository }}`, while Docusaurus `baseUrl`, `projectName`, edit links, search routes, and hard-coded portal links may still carry the old slug. Inventory and update those values, preserve repository history with the Gitea rename API, update the Git remote, and push a commit after the rename so Actions republishes to `/gondor-v1-gitea-pages/NEW_ORG/NEW_REPO`. Verify the exact post-rename `head_sha` has a successful Actions run and read back distinctive content from the renamed repository. If the browser reaches the configured OAuth boundary, treat that as route/auth evidence—not content evidence—and use an authorized session or internal backend for marker verification. Decide explicitly whether the old Pages directory should redirect, remain temporarily, or be removed; a repository rename does not clean the old static directory automatically.
### Dedicated homepage and native local search
For documentation sites that need a showcase-style product landing page rather than docs content at `/`:
1. Move the docs intro to `/overview` (or another explicit route).
2. Build the homepage in `src/pages/index.tsx` with `<Layout>`, semantic sections, and CSS Modules.
3. Keep changing registry counts generated from canonical data rather than hard-coding them in JSX.
4. Add `@easyops-cn/docusaurus-search-local` under `themes`, set `indexPages: true`, and include every docs plugin route in `docsRouteBasePath` (for example `['/', 'clients']`). Do not configure Ask AI or any external search provider when the requirement is native/offline search.
5. Verify the production build emits `build/search-index.json`, confirm representative terms from each docs plugin are present, then exercise a real browser query and open a result.
6. Treat push, successful Gitea Actions, and public Pages checks as part of completion; a local build alone is not a published result.
See `references/dedicated-homepage-local-search.md` for a known-good route, search, generated-summary, styling, and verification pattern.
### Setup Steps
```bash
# Step 1: Create in a temp directory (avoids "directory not empty" error)
npx pnpm create docusaurus@latest /tmp/docs-temp classic --package-manager pnpm --typescript
# Step 2: Install deps in temp dir
cd /tmp/docs-temp && npx pnpm install
# Step 3: Approve build scripts (required for @swc/core and core-js)
npx pnpm approve-builds @swc/core core-js
# Step 4: Copy into git repo (preserving .git)
cp -r /tmp/docs-temp/* /path/to/git-repo/
cp /tmp/docs-temp/.gitignore /path/to/git-repo/
# Step 5: Verify build
cd /path/to/git-repo && npx pnpm build
# Step 6: Cleanup
rm -rf /tmp/docs-temp
```
### Multi-Application Documentation Structure
For a docs site covering multiple autonomous applications:
```
docs/
├── intro/ # Landing page, app portfolio
├── applications/
│ └── app-name/
│ ├── index.md # Overview + constraints
│ ├── overview/
│ │ └── architecture.md # System design, tech stack
│ ├── module-1/
│ │ └── index.md # Module docs
│ └── module-n/
│ └── index.md
├── architecture/ # Platform-wide infra
│ └── overview.md
└── guides/
└── getting-started.md
```
## 3. Gitea Actions CI/CD
Workflow file goes in `.gitea/workflows/build.yml`:
```yaml
name: Build Documentation
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Cache pnpm
uses: actions/cache@v4
with:
path: $(pnpm store path --silent)
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: actions/upload-artifact@v4
if: github.ref == 'refs/heads/main'
with:
name: docusaurus-build
path: build/
```
**Note**: Gitea Actions uses the same syntax as GitHub Actions. Most `actions/*` work unchanged. Requires a Gitea runner registered for the org.
## 4. Key Gitea API Differences from GitHub
| Feature | GitHub | Gitea |
|---|---|---|
| Base URL | `api.github.com` | `gitea.lego-cloud.eu/api/v1` |
| Auth header | `Authorization: token X` | Same |
| Create org | `POST /orgs` | Same |
| Create org repo | `POST /orgs/O/repos` | Same |
| SSH clone | `git@github.com:O/R.git` | `ssh://git@gitea.lego-cloud.eu:30009/O/R.git` |
| Actions dir | `.github/workflows/` | `.gitea/workflows/` |
| Runner | GitHub-hosted | Self-hosted (must register) |
## 5. LEGO Cloud Pages Publishing
Although Gitea core has no built-in Pages feature, LEGO Cloud provides a confirmed external Pages platform backed by the TrueNAS-hosted Actions runner and Gondor Nginx/NFS.
### Docusaurus path configuration
For repository `ORG/REPO`:
```ts
url: 'https://pages.apps.lego-cloud.eu',
baseUrl: '/ORG/REPO/',
organizationName: 'ORG',
projectName: 'REPO',
```
The live URL is `https://pages.apps.lego-cloud.eu/ORG/REPO/`. Verify both the page and at least one generated `/ORG/REPO/assets/...` URL return HTTP 200.
### Confirmed runner publication workflow
The runner exposes the persistent Pages dataset inside each job container at `/gondor-v1-gitea-pages`. Publish Docusaurus `build/` contents to the repository-derived directory:
```yaml
- name: Publish to Pages
if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
shell: bash
run: |
set -euo pipefail
ORG_REPO='${{ gitea.repository }}'
case "$ORG_REPO" in */*) ;; *) exit 1 ;; esac
case "$ORG_REPO" in *..*|/*|*/|*//* ) exit 1 ;; esac
test -f build/index.html
pages_root="/gondor-v1-gitea-pages"
mountpoint -q "$pages_root" || {
echo "$pages_root is not a persistent host mount inside the Gitea Actions job container" >&2
exit 1
}
destination="$pages_root/$ORG_REPO"
rm -rf -- "$destination"
mkdir -p "$destination"
cp -a build/. "$destination/"
```
Use dependency-free `cp`, because the standard runner image may not include `rsync`. The `mountpoint` guard prevents a false-success write into an ephemeral job filesystem.
If `pnpm-workspace.yaml` exists for a root-only Docusaurus project, it must contain:
```yaml
packages:
- '.'
```
Without that field, pnpm 9 fails with `packages field missing or empty`.
The runner-side bind and authorization are documented in the `gitea-repository-operations` skill under `references/truenas-actions-pages.md`.
## 6. Gitea Core Pages Status
**Gitea does NOT have a built-in Pages feature** (as of v1.26.2). The GitHub issue (#23521) was a discussion that closed — actual implementation lives in a **separate standalone project** (`gitea/pages-server`) that must be deployed alongside Gitea. It is NOT baked into Gitea like GitHub Pages is baked into GitHub.
When the user asks "does Gitea have Pages?", the accurate answer is:
- No native equivalent in Gitea core
- `gitea/pages-server` exists but requires a separate deployment
- Recommended self-hosted alternative: **MinIO for static file hosting** (S3-compatible, bucket with public policy + `mc cp` in CI) or a thin Nginx/Caddy container
- MinIO caveat: no clean-URL routing natively → add a reverse proxy or use `trailingSlash: true` in Docusaurus config
## 6. Tailwind CSS + shadcn/ui Integration
**Recommended stack** — endorsed by Docusaurus maintainer team (used on docusaurus.io itself).
### Install
```bash
npx pnpm add -D tailwindcss postcss autoprefixer
npx pnpm add server-only clsx tailwind-merge @radix-ui/react-slot @docusaurus/theme-common
```
### tailwind.config.js
```js
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx,mdx}', './docs/**/*.{md,mdx}'],
darkMode: ['class'], // ThemeSynchronizer bridges data-theme → .dark
theme: { extend: { borderRadius: { lg:'0px', md:'0px', sm:'0px' } } },
corePlugins: { preflight: false }, // MUST — prevents fighting Infima CSS
};
```
### PostCSS plugin in docusaurus.config.ts
```ts
plugins: [
async function tailwindPlugin() {
return {
name: 'docusaurus-tailwindcss',
configurePostCSS(postcssOptions) {
postcssOptions.plugins.push(require('tailwindcss'));
postcssOptions.plugins.push(require('autoprefixer'));
return postcssOptions;
},
};
},
],
```
### ThemeSynchronizer — bridges Docusaurus ↔ shadcn dark mode
Create `src/theme/Layout/index.tsx` (swizzle wrap):
```tsx
import React, { useEffect } from 'react';
import Layout from '@theme-original/Layout';
import { useColorMode } from '@docusaurus/theme-common';
function ThemeSynchronizer(): null {
const { colorMode } = useColorMode();
useEffect(() => {
document.documentElement.classList.toggle('dark', colorMode === 'dark');
}, [colorMode]);
return null;
}
export default function LayoutWrapper(props) {
return <Layout {...props}><ThemeSynchronizer />{props.children}</Layout>;
}
```
### CSS variables for custom.css (add to @layer base)
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root { --radius: 0rem; /* sharp corners */ }
.dark { --radius: 0rem; }
}
```
### Pitfalls
- `@docusaurus/theme-common` is a transitive dep under pnpm — add it explicitly: `npx pnpm add @docusaurus/theme-common`
- shadcn v2+ requires `server-only` package: `npx pnpm add server-only`
- CSS variable namespaces don't conflict: `--background` / `--ifm-*` / `--tw-*` are all separate
- Infima can win specificity battles — use `!` prefix (e.g. `!text-red-500`) for overrides
### Sharp corners (user preference for Lego)
Set `--radius: 0rem` in CSS variables AND `border-radius: 0 !important` on all Infima selectors:
```css
:root {
--ifm-global-radius: 0;
--ifm-code-border-radius: 0;
--ifm-pre-border-radius: 0;
--ifm-alert-border-radius: 0;
--ifm-button-border-radius: 0;
--ifm-badge-border-radius: 0;
}
.button, code, .prism-code, div[class*='codeBlockContainer'],
.admonition, .pagination-nav__link { border-radius: 0 !important; }
```
## 7. Forced Dark Navbar (both light and dark modes)
To lock the navbar to dark regardless of color mode:
```css
.navbar {
background-color: #0a0a0a !important;
border-bottom: 1px solid #1f1f1f !important;
box-shadow: none !important;
}
.navbar__title { color: #ededed !important; }
.navbar__link { color: #a1a1a1 !important; }
.navbar__link:hover, .navbar__link--active { color: #ededed !important; background: transparent !important; }
.navbar-sidebar { background-color: #0a0a0a !important; }
```
The `!important` is required — Infima sets navbar background with high specificity.
## 8. Professional Platform Navigation Structure
For a multi-application platform docs site, use this top-level structure (not just a flat "Applications" list):
```
Introduction ← What this platform is, app portfolio table
Platform ← Infrastructure, CI/CD, networking, auth
Products ← Each application (with C4 + Modules sub-structure)
Engineering ← Monorepo structure, tech stack, conventions
Runbooks ← Operational procedures, incident response
```
In `sidebars.ts`:
```ts
const sidebars = {
docsSidebar: [
{ type: 'doc', id: 'intro/index', label: 'Introduction' },
{ type: 'category', label: 'Platform', items: ['platform/overview', 'platform/cicd'] },
{ type: 'category', label: 'Products', items: [
{ type: 'category', label: 'App Name', link: { type: 'doc', id: 'products/app/index' }, items: [
{ type: 'category', label: 'Architecture (C4)', items: ['products/app/c4/context','products/app/c4/containers','products/app/c4/components'] },
{ type: 'category', label: 'Modules', items: [...] },
]},
]},
{ type: 'category', label: 'Engineering', items: ['engineering/guidelines'] },
{ type: 'category', label: 'Runbooks', collapsed: true, items: ['runbooks/index'] },
],
};
```
## 9. Modern Docusaurus Styling
For a dark-first, modern technical docs theme (Vercel/Linear aesthetic):
### Font
```css
@import url('https://cdn.jsdelivr.net/npm/geist@1.3.0/dist/fonts/geist-sans/style.css');
@import url('https://cdn.jsdelivr.net/npm/geist@1.3.0/dist/fonts/geist-mono/style.css');
:root {
--ifm-font-family-base: 'Geist', 'Inter', system-ui, sans-serif;
--ifm-font-family-monospace: 'Geist Mono', 'Fira Code', monospace;
}
```
### Dark mode first in config
```ts
themeConfig: {
colorMode: {
defaultMode: 'dark',
disableSwitch: false,
respectPrefersColorScheme: true,
},
// ...
}
```
### Key --ifm- tokens for dark theme
```css
[data-theme='dark'] {
--ifm-background-color: #0a0a0a;
--ifm-background-surface-color: #111111;
--ifm-navbar-background-color: rgba(10,10,10,0.85);
--ifm-color-primary: #818cf8; /* indigo-400 */
--ifm-color-content: #ededed;
--ifm-color-content-secondary: #a1a1a1;
}
```
See `references/modern-docusaurus-theme.md` for the full CSS file.
## 7. draw.io Plugin Integration
```bash
npx pnpm add docusaurus-plugin-drawio raw-loader
```
Config (`docusaurus.config.ts`):
```ts
plugins: ['docusaurus-plugin-drawio'],
```
Usage in `.mdx` files — must use **raw-loader** to import the `.drawio` file:
```mdx
import Drawio from '@theme/Drawio';
import myDiagram from '!!raw-loader!./diagrams/my-diagram.drawio';
<Drawio content={myDiagram} />
```
**Pitfall**: `.drawio` files must be co-located with the `.mdx` file or in a relative path — they cannot be imported from `/static/`. Place them in e.g. `docs/architecture/diagrams/`.
## 8. C4 Architecture with Mermaid
Install Mermaid theme:
```bash
npx pnpm add @docusaurus/theme-mermaid
```
Config:
```ts
themes: ['@docusaurus/theme-mermaid'],
markdown: { mermaid: true },
themeConfig: {
mermaid: { theme: { light: 'neutral', dark: 'dark' } },
},
```
Mermaid supports C4 diagrams natively: `C4Context`, `C4Container`, `C4Component`. Use these for the C4 architecture levels. See `references/c4-docusaurus.md` for example syntax per level.
## 9. Multi-Application Docs Structure with C4
```
docs/
├── intro/index.md
├── applications/
│ └── app-name/
│ ├── index.md
│ ├── c4/
│ │ ├── context.md # L1 — system in the world
│ │ ├── containers.md # L2 — deployable units
│ │ └── components.md # L3 — internal structure
│ └── modules/ # Detailed module docs
├── architecture/
│ ├── overview.md
│ └── diagrams/ # .drawio files live here
└── guides/
└── getting-started.md
```
## Repository Collaborators and Admin-Level Access
Gitea repository permissions are `read`, `write`, and `admin`. When a user asks to make someone an "owner of this repository," use repository-level `admin` permission unless they explicitly ask for ownership of the entire organization.
```bash
# Confirm both the user and repository exist first
curl -fsS -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
"https://gitea.lego-cloud.eu/api/v1/users/USERNAME"
curl -fsS -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
"https://gitea.lego-cloud.eu/api/v1/repos/ORG/REPO"
# Add or update the collaborator with repository-admin permission
curl -fsS -X PUT \
-H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://gitea.lego-cloud.eu/api/v1/repos/ORG/REPO/collaborators/USERNAME" \
-d '{"permission":"admin"}'
# Verify the effective permission; do not rely only on the PUT returning 204
curl -fsS -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
"https://gitea.lego-cloud.eu/api/v1/repos/ORG/REPO/collaborators/USERNAME/permission"
```
Expected add/update response is HTTP `204`; the verification response must report `permission: "admin"` and the requested username.
## Verify a Private Repository Push
After pushing, verify both Git refs and the remote artifact:
```bash
git fetch origin
test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
```
A private repository's unauthenticated `/raw/branch/...` web URL may return `404`. That does not prove the file is absent. Verify through the authenticated Gitea API instead:
```bash
curl -fsS \
-H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" \
"https://gitea.lego-cloud.eu/api/v1/repos/ORG/REPO/raw/path/to/file.md?ref=main"
```
Read back a distinctive heading or marker from the response, not only the HTTP status. Before pushing into an actively maintained repository, run `git fetch origin` and inspect divergence. If the local branch is merely behind and remote changes do not overlap local edits, a fast-forward merge can preserve the working tree:
```bash
git merge --ff-only origin/main
```
Otherwise commit/stash safely, then rebase or merge normally; never overwrite concurrent remote work.
## Automated Git Push from Scripts / Cron
When pushing to Gitea from a non-interactive context (cron job, shell script), use the SSH key directly via `GIT_SSH_COMMAND`:
```bash
GIT_SSH_COMMAND="ssh -i /opt/data/home/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o BatchMode=yes" \
git push origin main
```
Always include `-o BatchMode=yes` — without it, SSH may hang waiting for interactive input when host key verification fails.
**No-op pattern** (safe to run every sync, only commits when something changed):
```bash
git add -A
if git diff --cached --quiet; then
echo "No changes."
exit 0
fi
CHANGED=$(git diff --cached --name-only | wc -l)
git commit -m "sync $(date -u '+%Y-%m-%d %H:%M UTC') — $CHANGED file(s)"
GIT_SSH_COMMAND="ssh -i /opt/data/home/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o BatchMode=yes" git push origin main
```
## Pitfalls (Continued)
10. **"This account is prohibited from signing in"**: The Gitea user tied to the API token has been marked *Prohibit Login* by an admin. Both API calls (`/api/v1/user`) and SSH pushes fail with this message. Fix: Gitea admin panel → Site Administration → User Accounts → find the user → uncheck "Prohibit Login" → Save. URL: `https://gitea.lego-cloud.eu/-/admin/users`. The API token itself remains valid once the account is re-enabled — no need to regenerate it.
11. **SSH key "Key check failed"**: If SSH push gives `error: Key check failed`, the SSH public key is either not registered on the Gitea account or the account is prohibited (see pitfall #10). Check: Gitea → User Settings → SSH/GPG Keys. The key at `/opt/data/home/.ssh/id_ed25519.pub` must appear there.
## Research and Address-Book Repositories
When the requested Gitea project maps a broad ecosystem, global infrastructure, organizations, or other changing domain, do not default to a Docusaurus application scaffold. Establish a source-backed research repository first:
1. Start with authoritative coordinating bodies, registries, and maintained public directories rather than claiming exhaustive participant coverage.
2. Separate human-readable `address-book/`, canonical `data/`, methodology/findings in `docs/`, unverified work in `research/`, and provenance policy in `sources/`.
3. Use a controlled functional taxonomy; avoid imposing a false hierarchy on decentralized ecosystems.
4. Include a dependency-free validator plus `make validate` and `make summary` targets.
5. Document source verification dates, evidence status, coverage limitations, and public-contact safety.
6. Run `git diff --cached --check` before committing, then verify the pushed ref and read back a distinctive remote artifact through the authenticated Gitea API.
See `references/research-address-book-repositories.md` for the full repository pattern, round-1 CSV schema, research method, and validation checklist.
## Verification
- `curl -s -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" https://gitea.lego-cloud.eu/api/v1/user` — confirms account is active (returns user JSON, not error message)
- `curl -s -H "Authorization: token $HL_V1_GITEA_ACCESS_TOKEN" https://gitea.lego-cloud.eu/api/v1/orgs/ORG` — org exists
- `npx pnpm build` — docs compile without errors
- Push triggers workflow (check Actions tab in Gitea UI)
## References
- `references/docusaurus-config-template.md` — full docusaurus.config.ts example
- `templates/gitea-actions-build.yml` — CI workflow template
- `references/modern-docusaurus-theme.md` — dark-first CSS theme (Geist font, indigo accent, forced dark navbar, sharp corners)
- `references/c4-docusaurus.md` — C4Context/C4Container/C4Component Mermaid syntax + sidebar structure
- `references/go-monorepo-conventions.md` — Go workspace monorepo layout, standard libraries, code conventions for SKIC apps
- `references/migrating-existing-markdown-corpora.md` — preserve canonical corpora while generating MDX-safe docs, stable routes, raw artifacts, and end-to-end Pages validation
- `references/dedicated-homepage-local-search.md` — separate a showcase landing page from docs routes, generate live homepage metrics, configure fully local search, avoid duplicate SearchBar instances, and verify public deployment
- `references/generated-evidence-registries.md` — generate conservative cross-client registries with stable identity, separate docs plugins, flat sidebars, route/count validation, and auth-aware Pages verification
- `references/research-led-evidence-registry-ia.md` — organize large entity/evidence portals around user tasks, generate scope and interpretation content from canonical data, and verify the resulting IA and deployment
- `references/reference-inspired-documentation-portals.md` — adapt a selected Docusaurus reference into an original portal, preserve explicit visual constraints, manage non-repeating shortlists, and verify dedicated homepage routes plus compiled sharp-corner rules
- `references/behavioral-acceptance-for-doc-portals.md` — requirement-to-proof gates for real theme switching, independent section sidebars, browser-local search, static-host query redirect handling, and deployment verification
- `references/multi-section-evidence-portals.md` — split major areas into independent sidebars, preserve a default docs instance, generate reusable evidence cards, document internal IDs, add per-record References indexes, and verify exact coverage
+86
View File
@@ -0,0 +1,86 @@
# Go Monorepo Conventions — SKIC Playground
## Directory Layout
```
skic-v1-playground/
├── documentation/ # Docusaurus site
├── stock-market-pro/ # Go service
│ ├── cmd/server/main.go
│ ├── internal/ # App-private packages
│ │ ├── ingestor/
│ │ ├── analysis/
│ │ ├── signals/
│ │ └── notifier/
│ ├── pkg/ # Public packages (if any)
│ ├── Dockerfile
│ ├── Makefile
│ └── go.mod
├── shared/ # Cross-app Go libraries
│ ├── discord/
│ ├── config/
│ └── telemetry/
├── infra/
│ └── docker-compose.yml
├── go.work # Go workspace — ties all modules together
└── Makefile # Root targets: build all, test all
```
## go.work File
```
go 1.22
use (
./stock-market-pro
./shared/discord
./shared/config
./shared/telemetry
)
```
Local modules reference each other without `replace` directives.
## Standard Go Libraries (SKIC stack)
| Package | Purpose |
|---|---|
| `net/http` | HTTP server/client |
| `encoding/json` | JSON |
| `database/sql` + `modernc.org/sqlite` | SQLite (dev) |
| `github.com/lib/pq` | PostgreSQL/TimescaleDB (prod) |
| `github.com/rs/zerolog` | Structured logging |
| `github.com/spf13/viper` | Config from env vars |
| `github.com/robfig/cron/v3` | Scheduled jobs |
| `golang.org/x/sync` | Concurrency utilities |
## Code Conventions
- **Error wrapping**: `fmt.Errorf("context: %w", err)` — never swallow errors
- **Logging**: zerolog structured JSON, no `fmt.Println` in prod
- **Tests**: table-driven, `testify/assert`, race detector on CI (`-race`)
- **Config**: env vars via Viper, validated at startup with explicit error
## Python as Sidecar
Python (pandas-ta, ta-lib, scikit-learn) runs as a **sidecar HTTP/gRPC service** when the Go ecosystem is thin for data/ML. The Go service calls localhost endpoints.
## Makefile Targets (per app)
```makefile
build: go build -o bin/server ./cmd/server
test: go test ./... -race -cover
lint: golangci-lint run
docker: docker build -t $(APP_NAME):$(VERSION) .
run: source .env && go run ./cmd/server
```
## Adding a New App
1. `mkdir skic-v1-playground/<app-name>/`
2. `go mod init gitea.lego-cloud.eu/skic-v1-playground/<app-name>`
3. Add `use ./<app-name>` to `go.work`
4. Copy `.gitea/workflows/build.yml` from template
5. Create multi-stage Dockerfile: `golang:1.22-alpine` → `alpine:3.19`
6. Add docs under `documentation/docs/products/<app-name>/` with C4 structure
7. Add to `documentation/src/pages/index.tsx` applications array
+406
View File
@@ -0,0 +1,406 @@
# Local YAML Data Database
Use this pattern when structured documentation data must live in the documentation repository, remain reviewable through Git, and drive React components and MDX pages without duplicating entity content inside MDX.
## Contract
The source database is a local `data/` tree. It is not a runtime service and it is not a collection of generated MDX pages.
- Every database source file uses the `.yml` extension.
- Keep one record per file.
- Keep one `index.yml` in every leaf collection.
- Requirements live only under dedicated requirement folders.
- Architectural decisions live under the exact folder `data/architecture-decisions/`.
- Stable IDs, not titles or paths, define relationships.
- Load and validate data during the Docusaurus build-time phase.
- React components consume typed build data; they do not read the filesystem in the browser.
- MDX composes components and explanatory prose; it does not duplicate structured record content.
A JSON module created by Docusaurus inside `.docusaurus/` is an allowed generated build artifact. It is not a maintained database source and must not be committed as authoritative data.
## Required structure
```text
data/
├── database.yml
├── epics/
│ ├── index.yml
│ └── epic-001.yml
├── features/
│ ├── index.yml
│ └── feature-001.yml
├── tasks/
│ ├── index.yml
│ └── task-001.yml
├── requirements/
│ ├── functional/
│ │ ├── index.yml
│ │ └── fr-001.yml
│ └── non-functional/
│ ├── index.yml
│ └── nfr-001.yml
├── architecture-decisions/
│ ├── index.yml
│ └── adr-001.yml
└── indexes/
├── epic-features.yml
├── feature-tasks.yml
├── feature-requirements.yml
└── feature-architecture-decisions.yml
```
Do not combine functional requirements, non-functional requirements, architectural decisions, or delivery entities into one catch-all collection. Do not create a generic decision folder. The explicit folders are part of the data contract.
`database.yml` declares the database schema version and enabled collections:
```yaml
schema_version: 1
collections:
- epics
- features
- tasks
- requirements/functional
- requirements/non-functional
- architecture-decisions
```
## Stable identifiers and filenames
Use lowercase stable IDs with a type prefix and numeric sequence:
| Entity | ID example | Filename |
|---|---|---|
| Epic | `epic-001` | `epic-001.yml` |
| Feature | `feature-001` | `feature-001.yml` |
| Functional requirement | `fr-001` | `fr-001.yml` |
| Non-functional requirement | `nfr-001` | `nfr-001.yml` |
| Task | `task-001` | `task-001.yml` |
| Architectural decision | `adr-001` | `adr-001.yml` |
The filename stem must equal the record's `id`. Never reuse an ID, even after archiving a record. Renaming a title must not change its ID.
## Entity records
### Epic
```yaml
id: epic-001
title: Project lifecycle registry
status: in-progress
summary: >
Provide a traceable registry of project lifecycle information.
owners:
- root-at-skic
tags:
- governance
- lifecycle
```
### Feature
A Feature belongs to one Epic. The Feature owns that relationship through `epic_id`.
```yaml
id: feature-001
title: Display project lifecycle state
status: in-progress
epic_id: epic-001
depends_on_feature_ids:
- feature-000
roadmap:
start: 2026-08-01
end: 2026-08-21
lane: portal
progress: 60
summary: >
Display the current lifecycle state and supporting evidence.
owners:
- root-at-skic
```
`depends_on_feature_ids` is the canonical Feature dependency field. `roadmap` is optional unless the documentation exposes a date-based roadmap. When present, validate ISO dates, require `end` not to precede `start`, require a stable lane, and constrain `progress` to 0–100. React Flow adapters may derive dependency edges and Gantt-like positions from these fields; TSX and MDX must not maintain separate schedules or dependency lists.
### Task
A Task belongs to one Feature. The Task owns that relationship through `feature_id`.
```yaml
id: task-001
title: Implement the lifecycle state component
status: in-backlog
feature_id: feature-001
summary: >
Render lifecycle status from the local data source.
```
### Functional requirement
Functional requirements remain in `data/requirements/functional/`. A requirement owns its many-to-many Feature mapping through `feature_ids`.
```yaml
id: fr-001
type: functional
title: Display lifecycle state
status: approved
statement: >
The portal must display the current lifecycle state for every project.
feature_ids:
- feature-001
- feature-004
```
### Non-functional requirement
Non-functional requirements remain in `data/requirements/non-functional/` and use the same relationship field.
```yaml
id: nfr-001
type: non-functional
category: performance
title: Resolve documentation data during the static build
status: approved
statement: >
Documentation pages must not require a runtime database service.
feature_ids:
- feature-001
- feature-002
```
### Architectural decision
Architectural decisions remain in `data/architecture-decisions/`. A decision owns its many-to-many Feature mapping through `feature_ids`.
```yaml
id: adr-001
title: Use local YAML records for structured documentation
status: accepted
date: 2026-08-14
context: >
Structured entities must be reused across several documentation views.
decision: >
Store authoritative records in local YAML files and resolve them during
the static documentation build.
consequences:
- Documentation changes remain Git-reviewable.
- The deployed site does not require a database service.
feature_ids:
- feature-001
- feature-002
```
## Relationship ownership
Store each relationship in one canonical direction only:
| Relationship | Cardinality | Canonical owner |
|---|---:|---|
| Epic to Feature | 1:n | Feature stores `epic_id` |
| Feature to Task | 1:n | Task stores `feature_id` |
| Requirement to Feature | n:m | Requirement stores `feature_ids` |
| Architectural decision to Feature | n:m | Architectural decision stores `feature_ids` |
Do not also store `feature_ids` on Epics, `task_ids` on Features, or reverse requirement and decision IDs on Features. Generate reverse lookups in `data/indexes/`. This prevents two editable fields from disagreeing about the same relationship.
## Collection indexes
Each leaf collection has an `index.yml` containing stable IDs and relative record paths. Sort entries by ID.
```yaml
schema_version: 1
items:
- id: feature-001
file: feature-001.yml
- id: feature-002
file: feature-002.yml
```
Collection indexes are generated and committed. They make reviews explicit and allow tools to discover records without relying on filesystem ordering.
## Relationship indexes
Relationship indexes are derived from canonical record fields. For example, `data/indexes/feature-requirements.yml`:
```yaml
schema_version: 1
features:
feature-001:
functional:
- fr-001
non_functional:
- nfr-001
feature-002:
functional: []
non_functional:
- nfr-001
```
And `data/indexes/feature-architecture-decisions.yml`:
```yaml
schema_version: 1
features:
feature-001:
architecture_decisions:
- adr-001
feature-002:
architecture_decisions:
- adr-001
```
Never edit derived relationship indexes manually. Regenerate them from source records.
## Maintenance commands
Every adopting documentation repository must expose these commands:
```text
pnpm data:index
pnpm data:validate
pnpm data:check
```
- `pnpm data:index` deterministically rebuilds collection and relationship indexes.
- `pnpm data:validate` validates schemas, IDs, files, and relationship integrity.
- `pnpm data:check` regenerates indexes in memory or a temporary directory and fails when committed indexes are stale.
Index generation must not add timestamps or machine-specific paths. Running it twice against unchanged records must produce byte-identical output.
## Validation requirements
Validation must fail for:
- any source database file whose extension is not `.yml`;
- a missing collection `index.yml`;
- a duplicate record ID;
- a filename stem that differs from the record ID;
- an unknown entity type, status, or requirement category;
- a required field with an empty value;
- a dangling `epic_id`, `feature_id`, or entry in `feature_ids`;
- a dangling entry in `depends_on_feature_ids`;
- an invalid roadmap date, reversed date range, missing lane, or progress outside 0–100;
- an index entry whose record is missing;
- a record missing from its collection index;
- a stale relationship index;
- a record placed in the wrong collection;
- an architectural decision outside `data/architecture-decisions/`;
- a functional or non-functional requirement outside its dedicated folder.
Run validation before the Docusaurus build and in Gitea Actions. A successful site build is not sufficient if data validation was skipped.
## Docusaurus build-time loading
Browsers cannot safely discover repository files at runtime. Implement a local Docusaurus plugin that:
1. reads `data/database.yml` and all collection indexes;
2. parses the referenced `.yml` records;
3. validates records and relationships;
4. constructs typed entity maps and reverse indexes;
5. publishes one immutable client data module with `actions.createData`;
6. optionally registers generic routes for entity details and traceability views.
Keep parsing, validation, and relationship resolution on the Node build side. Do not bundle filesystem APIs or independent YAML parsers into each React component.
A minimal plugin shape is:
```ts
export default function localYamlDataPlugin() {
return {
name: 'local-yaml-data',
async loadContent() {
return loadAndValidateDatabase('data');
},
async contentLoaded({content, actions}) {
await actions.createData(
'documentation-database.json',
JSON.stringify(content),
);
},
};
}
```
Treat the generated module as derived data. Rebuild it from the YAML source on every production build.
## Typed React access
Provide one data context and stable hooks rather than letting components know storage paths:
```ts
const epic = useEpic('epic-001');
const features = useFeaturesForEpic('epic-001');
const tasks = useTasksForFeature('feature-001');
const requirements = useRequirementsForFeature('feature-001');
const decisions = useArchitectureDecisionsForFeature('feature-001');
const traceability = useFeatureTraceability('feature-001');
```
Recommended components:
- `EpicTree`
- `EpicDetails`
- `FeatureDetails`
- `FeatureTasks`
- `RequirementDetails`
- `RequirementsForFeature`
- `ArchitectureDecisionDetails`
- `ArchitectureDecisionsForFeature`
- `RequirementFeatureMatrix`
- `DecisionFeatureMatrix`
- `TraceabilityMatrix`
Components receive stable IDs or filter properties. They must render a clear missing-record state rather than silently returning empty output.
## MDX composition
MDX pages provide page purpose, explanation, and component placement. They must not copy record fields that already exist in the database.
```mdx
---
title: "Explore delivery scope"
description: "Inspect Epics, Features, Tasks, requirements, and decisions."
---
import EpicTree from '@site/src/components/documentation-data/EpicTree';
import TraceabilityMatrix from '@site/src/components/documentation-data/TraceabilityMatrix';
<EpicTree />
<TraceabilityMatrix />
```
Use a generic `FeatureDetails` component or generated route for individual Features instead of generating and maintaining one content-heavy MDX file per Feature.
## Change workflow
For every data change:
1. Add or edit the authoritative record in its dedicated folder.
2. Preserve existing stable IDs.
3. Run `pnpm data:index`.
4. Review source and derived index changes together.
5. Run `pnpm data:validate`.
6. Run `pnpm data:check`.
7. Run the Docusaurus typecheck and production build.
8. Exercise the affected React view in a browser.
9. Commit records and indexes together.
10. Verify the Gitea Actions run and deployed documentation route.
For schema evolution, increment `schema_version`, migrate all records and indexes atomically, and keep the loader's error message explicit about unsupported versions.
## Acceptance checklist
- [ ] Database source uses only `.yml` files.
- [ ] Requirements are separated into functional and non-functional folders.
- [ ] Architectural decisions use exactly `data/architecture-decisions/`.
- [ ] Every entity has one stable-ID record file.
- [ ] Every leaf collection has a deterministic `index.yml`.
- [ ] Canonical relationship fields implement Epic → Feature → Task and n:m mappings to requirements and architectural decisions.
- [ ] Feature dependencies and optional roadmap scheduling remain in source YAML and pass referential and date-range validation.
- [ ] Reverse indexes are generated, committed, and stale-index checked.
- [ ] Referential validation rejects duplicate and dangling IDs.
- [ ] Docusaurus loads data at build time.
- [ ] React components consume typed hooks rather than parsing source files.
- [ ] MDX contains composition and explanatory content rather than duplicate records.
- [ ] Data checks, typecheck, production build, browser view, Actions, and deployed route are verified.
@@ -0,0 +1,95 @@
# Migrating an Existing Markdown Corpus to Docusaurus
Use this pattern when an established evidence, registry, research, or documentation repository must become a Docusaurus site without turning generated presentation files into the source of truth.
## Preserve the canonical corpus
Keep canonical files in their existing paths when validators, scheduled jobs, links, or provenance depend on them. Do not bulk-move or inject front matter into hundreds of source documents merely to satisfy Docusaurus.
Recommended split:
```text
clients/ or records/ # canonical Markdown
INDEX.md # canonical registry
source/ # supplied/source artifacts
scripts/validate_registry.py # canonical content validation
site-docs/ # authored website guidance
.generated-docs/ # ignored, generated MDX-safe copies
static/raw/ # ignored, staged byte-identical artifacts
scripts/generate_site_content.py
scripts/publish_raw_artifacts.py
scripts/validate_site_build.py
```
Configure a second `@docusaurus/plugin-content-docs` instance for `.generated-docs/`. Build scripts should regenerate it from canonical sources every time.
## Strict generated-copy transform
The generator should:
1. Parse the canonical index strictly and require the expected IDs, paths, statuses, and order.
2. Verify every indexed source exists.
3. Recreate the generated tree from scratch.
4. Apply only an explicit compatibility allowlist.
5. Fail if a new or unexpected transform is needed.
6. Generate navigation/index content from the same canonical registry.
A common MDX hazard is raw `<br>` in Markdown tables. Convert it to `<br />` only in generated copies and assert the exact files/replacement counts. Literal placeholders such as `<ID>` in templates should remain outside the compiled docs tree or be escaped in a generated display copy.
## Preserve stable numeric routes
Docusaurus treats numeric filename prefixes as sidebar ordering metadata and can remove them from inferred routes. For canonical file `001-client-name.md`, prepend generated front matter:
```yaml
---
slug: /001-client-name
---
```
This preserves `/clients/001-client-name/` while leaving the canonical file untouched.
For hundreds of documents, generate category directories (for example `001-050`, `051-100`) with `_category_.json`. Keep the explicit `slug` so grouping changes navigation without changing public routes.
## Raw artifact publication
For provenance-sensitive repositories, stage byte-identical canonical files under `static/raw/` before `docusaurus build` and generate a checksum manifest. Validate hashes between canonical files and `build/raw/` after the build.
If `trailingSlash: true` causes Docusaurus broken-link checking to append `/` to extensionless static paths, publish/link a manifest with an extension such as `SHA256SUMS.txt`, or use the final absolute Pages URL. Do not relax `onBrokenLinks: 'throw'` merely to hide this.
## Build pipeline
A robust package script is:
```json
{
"scripts": {
"validate": "python3 scripts/generate_site_content.py && python3 scripts/validate_registry.py && tsc --noEmit",
"build": "python3 scripts/generate_site_content.py && python3 scripts/validate_registry.py && python3 scripts/publish_raw_artifacts.py && docusaurus build && python3 scripts/validate_site_build.py"
}
}
```
Commit `pnpm-lock.yaml`, pin one Docusaurus release across packages, and keep generated directories ignored.
## Site-build validation
After the real production build, assert:
- expected number of rendered document routes;
- representative first, middle, compatibility-transformed, longest-path, and last routes;
- expected production `baseUrl` in emitted HTML/assets;
- generated directory/index record count;
- byte equality or SHA-256 equality for every published raw artifact;
- homepage, directory, representative deep links, and checksum manifest return HTTP 200 under the exact subpath.
Also inspect the site visually at the production subpath. For large tables, enforce horizontal scrolling without removing columns. For a square UI, use both Infima radius variables and a final global `border-radius: 0 !important` override.
## Actions and continuity
The Gitea workflow must run source validation, project validation, the real build, and build-output validation before publishing. Observe the actual Actions conclusion and then verify the public site; workflow YAML presence is not completion.
Before conversion, pause scheduled writers. After publication:
- update their workdir and remote URL;
- tell them to edit canonical files only, never generated site output;
- resume only after local worktrees and remote synchronization are verified.
+109
View File
@@ -0,0 +1,109 @@
# Modern Docusaurus Dark Theme — CSS Reference
Dark-first, Vercel/Linear-inspired theme built for SKIC Playground docs.
Full source lives at `/opt/data/documentation/src/css/custom.css`.
## Design Decisions
- **Font**: Geist (by Vercel) via CDN + Geist Mono for code
- **Accent**: Indigo `#6366f1` (light) / `#818cf8` (dark)
- **Background**: `#0a0a0a` (near-black, not pure black)
- **Surface**: `#111111` for cards, sidebars
- **Text**: `#ededed` primary, `#a1a1a1` secondary
## Key Overrides
### Dark mode base palette
```css
[data-theme='dark'] {
--ifm-color-primary: #818cf8;
--ifm-background-color: #0a0a0a;
--ifm-background-surface-color: #111111;
--ifm-navbar-background-color: rgba(10, 10, 10, 0.85);
--ifm-color-content: #ededed;
--ifm-color-content-secondary: #a1a1a1;
--ifm-color-emphasis-100: #1a1a1a;
--ifm-color-emphasis-200: #222222;
--ifm-color-emphasis-300: #333333;
}
```
### Typography
```css
:root {
--ifm-font-family-base: 'Geist', 'Inter', system-ui, sans-serif;
--ifm-font-family-monospace: 'Geist Mono', 'Fira Code', monospace;
--ifm-font-size-base: 15px;
--ifm-line-height-base: 1.7;
--ifm-heading-letter-spacing: -0.02em;
--ifm-h1-font-size: 2.25rem;
}
h2 { border-bottom: 1px solid var(--ifm-color-emphasis-200); }
```
### Glassmorphic Navbar
```css
.navbar {
border-bottom: 1px solid var(--ifm-color-emphasis-200);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
[data-theme='dark'] .navbar { background: rgba(10, 10, 10, 0.85); }
```
### Table Styling
```css
table { border: 1px solid var(--ifm-color-emphasis-200); border-radius: 8px; overflow: hidden; }
thead { background: var(--ifm-color-emphasis-100); }
th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em; }
tbody tr:hover { background: var(--ifm-color-emphasis-100); }
```
### Hero Section (homepage)
```css
.heroTitle {
background: linear-gradient(135deg, #ffffff 0%, #a5b4fc 60%, #818cf8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: clamp(2.5rem, 5vw, 3.75rem);
font-weight: 800;
letter-spacing: -0.04em;
}
```
### App Cards (dark glass style)
```css
.appCard {
background: #111111;
border: 1px solid #222222;
border-radius: 12px;
transition: all 0.15s ease;
}
.appCard:hover {
border-color: #6366f1;
background: #161625;
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(99,102,241,0.12);
}
```
## Prism Code Theme
Use `oneDark` for both light and dark (visually consistent in docs):
```ts
prism: {
theme: prismThemes.oneDark,
darkTheme: prismThemes.oneDark,
additionalLanguages: ['bash', 'python', 'sql', 'yaml', 'json', 'typescript', 'docker'],
},
```
## Announcement Bar
```ts
announcementBar: {
id: 'wip',
content: '🚧 Under active development',
backgroundColor: '#1a1a2e',
textColor: '#818cf8',
isCloseable: true,
},
```
@@ -0,0 +1,91 @@
# Multi-section evidence portals in Docusaurus
Use this pattern when top-level areas such as Overview, Clients, Systems, Methodology, and Data are distinct user tasks and a shared sidebar makes navigation noisy.
## Dedicated docs instances
Give each top-level area its own `@docusaurus/plugin-content-docs` instance, content path, route base, and sidebar file. One instance must remain the default plugin (omit `id`) because themes such as local search may call the default docs hooks while rendering global pages, search, or 404 routes. Assign explicit IDs only to the additional instances.
For each sidebar, use an independently named autogenerated root:
```ts
const sidebars = {
sectionSidebar: [{type: 'autogenerated', dirName: '.'}],
};
```
When moving generated indexes into their section directories:
- give each index `slug: /` within its plugin;
- update navbar, footer, homepage, and cross-document links;
- include every route base in local-search configuration;
- remove obsolete shared docs only after the new routes exist;
- validate each top-level route by a distinctive content marker after production build.
## Large registry sidebar hierarchy
A dedicated sidebar is not sufficient if hundreds of dossiers remain at its root. For large entity sections, use exactly two first-level entries:
1. **Overview** — the section's registry/index document.
2. **Registry** — an expandable category containing every individual dossier in stable domain order.
```ts
const sidebars = {
entitiesSidebar: [
{type: 'doc', id: 'entity-directory', label: 'Overview'},
{
type: 'category',
label: 'Registry',
collapsed: false,
collapsible: true,
items: registryItems,
},
],
};
```
For generated content, build `registryItems` deterministically from canonical IDs or generated filenames. Be careful when deriving Docusaurus document IDs from filenames with numeric prefixes:
- a filename such as `001-client-name.md` commonly resolves to document ID `client-name`, because Docusaurus treats `001-` as a position prefix;
- if removing the generated position prefix leaves a basename beginning with a digit, Docusaurus may retain the complete filename ID—for example, `0002-112-mobile-application.md` may resolve to `0002-112-mobile-application`, not `112-mobile-application`;
- the safest new-generator design is an explicit stable `id` in frontmatter;
- for existing generators, derive IDs with the observed rule and verify both an ordinary dossier and the first numeric-leading dossier in the production build.
After building, inspect rendered section indexes—not only configuration source—and assert that `Overview`, `Registry`, and the first several ordered dossier labels are present. Also retain exact route-count validation for all dossiers.
## Reusable evidence cards
Wide relationship/evidence tables become difficult to scan. Generate MDX pages that import a reusable React card component and pass structured props for entity, relationship, context, contractor, confidence, and source links. Keep the component generic and keep evidence extraction in the generator.
Requirements:
- preserve claim-level direct links;
- render confidence and role explicitly;
- retain a link to the canonical entity dossier;
- use responsive semantic markup (`article`, `header`, `dl`, lists);
- preserve the site's shape language, including square edges when required;
- validate that all generated records contain cards and that the old table signature is absent.
## Generated identifier provenance
If registry identifiers are hash-derived, label them as internal generated IDs everywhere users encounter them: registry introduction, record page, methodology, and provenance/data documentation. State the exact derivation (for example, the first ten hexadecimal characters of a SHA-1 digest of the canonical identity key), its determinism boundary, and that it is not a public-source identifier.
## Per-record reference indexes
A record should preserve precise claim-level citations and also expose a de-duplicated `## References` index for scanning and reuse.
- Client pages: extract all direct Markdown URLs from the canonical dossier while generating the MDX copy; preserve first-seen label/order and de-duplicate by URL.
- Derived system pages: combine direct URLs from every source observation with canonical system-analysis evidence URLs; de-duplicate by URL.
- Do not replace claim-level links with the index.
- If there is no URL, say so explicitly rather than inventing one.
- Validate exact coverage counts: every generated entity dossier and every generated derived-record dossier must have a References heading.
## Verification sequence
1. Run source generation and schema validation.
2. Run TypeScript checks.
3. Run the real production build and route validator.
4. Assert exact dossier/reference/card counts and absence of obsolete table signatures.
5. Serve the build locally and request every top-level index plus representative entity and derived-record routes, checking distinctive markers.
6. Commit/push, prove local/remote SHA equality, inspect the Actions run matching that SHA, and verify deployed content when the protected backend is reachable.
7. Treat expected public authentication redirects separately from content publication.
+409
View File
@@ -0,0 +1,409 @@
# React Flow Documentation Visualizations
Use [React Flow](https://reactflow.dev/) when structured documentation data needs an interactive spatial view: roadmaps, Gantt-like timelines, dependency graphs, or traceability maps between Features, requirements, and architectural decisions. React Flow is a renderer and interaction layer, not the authoritative data store and not a built-in layout engine.
This capability assumes the local YAML data pattern in [local-yaml-data-database.md](local-yaml-data-database.md). Build nodes and edges from validated source YAML records. Never maintain a second copy of roadmap or relationship data inside TSX or MDX.
## Appropriate uses
Use React Flow for:
- a roadmap organized by Epic, lane, status, release, or date;
- a Gantt-like roadmap where time determines horizontal position and lane determines vertical position;
- Feature-to-Feature dependencies;
- Requirement-to-Feature n:m traceability;
- architectural decision-to-Feature n:m traceability;
- a combined, filterable delivery or architecture dependency map.
Do not use React Flow for ordinary prose, small static tables, or a linear list with no useful spatial relationship. Keep a semantic table fallback for every visualization so the information remains searchable, printable, and accessible without client-side JavaScript.
## Official baseline
Use the current package name documented by React Flow:
```text
pnpm add @xyflow/react
```
Import the required base stylesheet once in the visualization component or shared theme entry:
```ts
import '@xyflow/react/dist/style.css';
```
Relevant official guidance:
- [Quick Start](https://reactflow.dev/learn)
- [Layouting overview](https://reactflow.dev/learn/layouting/layouting)
- [Accessibility](https://reactflow.dev/learn/advanced-use/accessibility)
- [Server-side rendering](https://reactflow.dev/learn/advanced-use/ssr-ssg-configuration)
- [Testing](https://reactflow.dev/learn/advanced-use/testing)
React Flow does not provide one automatic graph layout. Its official layouting guide describes external options including Dagre, D3, and ELK. Choose and document the layout deliberately.
## Component boundary
Keep data loading, graph adaptation, layout, and rendering separate:
```text
data/**/*.yml
↓ build-time validation
DocumentationDatabase
↓ pure adapter
GraphModel { nodes, edges, fallbackRows, legend }
↓ pure layout
PositionedGraphModel
↓ React component
RoadmapFlow | DependencyMap
↓ MDX composition
Documentation page
```
Recommended source structure:
```text
src/components/documentation-data/
├── react-flow/
│ ├── DocumentationFlow.tsx
│ ├── RoadmapFlow.tsx
│ ├── DependencyMap.tsx
│ ├── nodes/
│ │ ├── FeatureNode.tsx
│ │ ├── RequirementNode.tsx
│ │ └── ArchitectureDecisionNode.tsx
│ ├── adapters/
│ │ ├── roadmapGraph.ts
│ │ ├── featureDependencyGraph.ts
│ │ ├── requirementFeatureGraph.ts
│ │ └── decisionFeatureGraph.ts
│ ├── layout/
│ │ ├── ganttLayout.ts
│ │ └── dependencyLayout.ts
│ └── DocumentationFlow.module.css
└── fallbacks/
├── RoadmapTable.tsx
└── TraceabilityTable.tsx
```
Adapters and layout functions must be pure TypeScript functions. Test them without a browser. React components receive already validated records or a typed graph model; they do not parse files.
## Docusaurus client boundary
React Flow 12 supports server rendering when node dimensions and handle positions are supplied. That is an advanced path. For normal interactive Docusaurus documentation, use Docusaurus `BrowserOnly` to avoid hydration differences and browser-global failures:
```tsx
import BrowserOnly from '@docusaurus/BrowserOnly';
import type {Props} from './DependencyMap';
export default function DependencyMapClient(props: Props) {
return (
<BrowserOnly fallback={<div>Loading dependency map…</div>}>
{() => {
const DependencyMap = require('./DependencyMap').default;
return <DependencyMap {...props} />;
}}
</BrowserOnly>
);
}
```
Use `@docusaurus/BrowserOnly`, not an unguarded `window` check scattered through components. If server-rendered graph HTML is an explicit requirement, follow the official React Flow 12 SSR guidance and provide deterministic node `width` and `height`, handle positions, and initial viewport data.
## Required canvas sizing
React Flow requires a parent with explicit dimensions. Never rely on content height:
```css
.canvas {
width: 100%;
height: min(72vh, 760px);
min-height: 420px;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 0;
}
:global(.react-flow__node),
:global(.react-flow__controls-button),
:global(.react-flow__minimap) {
border-radius: 0;
}
```
Use sharp edges throughout. Do not introduce pill nodes, rounded cards, rounded controls, or rounded filter inputs.
## Shared read-only renderer
Documentation views are read-only unless an editing workflow is separately approved. Disable accidental graph mutation:
```tsx
import {
Background,
Controls,
MiniMap,
ReactFlow,
ReactFlowProvider,
type Edge,
type Node,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
export function DocumentationFlow({nodes, edges}: {
nodes: Node[];
edges: Edge[];
}) {
return (
<div className={styles.canvas} aria-label="Documentation dependency map">
<ReactFlowProvider>
<ReactFlow
nodes={nodes}
edges={edges}
fitView
nodesDraggable={false}
nodesConnectable={false}
deleteKeyCode={null}
nodesFocusable
edgesFocusable
disableKeyboardA11y={false}
minZoom={0.35}
maxZoom={1.8}
proOptions={{hideAttribution: false}}
>
<Controls showInteractive={false} />
<MiniMap pannable zoomable />
<Background />
</ReactFlow>
</ReactFlowProvider>
</div>
);
}
```
Keep attribution behavior compliant with the installed package's license and React Flow terms. Do not hide attribution merely for visual preference.
## Roadmap and Gantt-like layout
React Flow is not a dedicated Gantt package. Implement a Gantt-like roadmap as a deterministic node layout over validated scheduling fields.
Recommended Feature source fields:
```yaml
id: feature-001
title: Display project lifecycle state
epic_id: epic-001
status: in-progress
depends_on_feature_ids:
- feature-000
roadmap:
start: 2026-08-01
end: 2026-08-21
lane: portal
progress: 60
```
Layout rules:
1. Parse dates in one declared timezone and reject invalid ranges.
2. Compute `x` from `start` relative to the roadmap's minimum date.
3. Compute node width from the duration between `start` and `end`.
4. Compute `y` from the stable lane order in data or configuration.
5. Draw Feature dependency edges from `depends_on_feature_ids`.
6. Render release or milestone markers as non-connectable custom nodes.
7. Preserve a consistent time scale while zooming; include a visible date-axis component outside the graph viewport.
8. Provide filters for Epic, status, lane, release, and owner without mutating source data.
9. Provide a table fallback ordered by lane, start date, end date, and stable Feature ID.
Do not let dragging persist a new schedule. A documentation reader moving a node is not a data edit. If manual positioning is required for a non-time-based roadmap, store approved coordinates in source YAML and validate them.
For a roadmap without dates, use a phase or status-column layout. Keep phases in source data and calculate positions deterministically. Do not infer ordering from file enumeration.
## Dependency layouts
### Feature dependencies
Build one node per Feature and one directed edge per `depends_on_feature_ids` entry. Reject missing Feature IDs. Detect cycles and either fail validation for a declared DAG or render cycles with an explicit warning when cyclic relationships are valid domain data.
For ordinary directed acyclic Feature graphs, use Dagre:
```text
pnpm add @dagrejs/dagre
```
Dagre is appropriate for a compact, deterministic directed layout. Set node dimensions before layout and map Dagre's center-based coordinates to React Flow positions.
### Complex or grouped dependencies
For larger maps with nested Epics, compound nodes, multiple ports, or stronger edge-routing requirements, use ELK:
```text
pnpm add elkjs
```
ELK layout is asynchronous. Resolve it before rendering the final graph and show a stable loading state. Do not run a new layout on every React render.
### Requirement-to-Feature traceability
Create typed nodes and edges from canonical requirement `feature_ids`:
```text
Functional Requirement ── satisfies/constrains ──> Feature
Non-functional Requirement ── constrains ──> Feature
```
Use distinct node types, edge styles, labels, and a visible legend. Filters must distinguish functional and non-functional requirements. Do not encode meaning only with color.
### Architectural decision-to-Feature traceability
Create typed nodes and edges from canonical architectural decision `feature_ids`:
```text
Architectural Decision ── governs/affects ──> Feature
```
Display decision status and link each node to its canonical documentation route. A superseded decision remains traceable but must be visually and textually identified as superseded.
### Combined maps
A combined map may include Features, requirements, and architectural decisions, but it must start with a constrained default scope. Large all-record graphs become unreadable and expensive. Require one or more of:
- selected Epic;
- selected Feature;
- selected requirement type;
- selected decision status;
- bounded dependency depth;
- search result or explicit ID list.
Apply filtering before layout so hidden nodes do not consume space.
## Adapter contract
A graph adapter should return all rendering and fallback information from one source:
```ts
export type DocumentationGraph = {
nodes: Node<DocumentationNodeData>[];
edges: Edge<DocumentationEdgeData>[];
fallbackRows: TraceabilityRow[];
legend: LegendItem[];
warnings: GraphWarning[];
};
export function requirementFeatureGraph(
database: DocumentationDatabase,
filter: RequirementFeatureFilter,
): DocumentationGraph;
```
Requirements for every adapter:
- stable node IDs equal stable database IDs;
- stable edge IDs derived from source type, source ID, relation, and target ID;
- no random coordinates or IDs;
- no filesystem access;
- no mutation of database records;
- explicit behavior for missing, filtered, superseded, or cyclic records;
- table fallback rows derived in the same function as nodes and edges.
## MDX composition
MDX chooses the view and scope. It does not define nodes or edges:
```mdx
---
title: "Explore Feature dependencies"
description: "Inspect Feature dependencies and related requirements."
---
import DependencyMapClient from '@site/src/components/documentation-data/react-flow/DependencyMapClient';
import TraceabilityTable from '@site/src/components/documentation-data/fallbacks/TraceabilityTable';
<DependencyMapClient
mode="feature-requirements"
epicId="epic-001"
/>
<TraceabilityTable
mode="feature-requirements"
epicId="epic-001"
/>
```
The map and table must consume the same adapter output or the same normalized selector so they cannot disagree.
## Accessibility
React Flow provides keyboard and screen-reader support. Preserve it:
- keep `nodesFocusable` and `edgesFocusable` enabled;
- keep `disableKeyboardA11y={false}`;
- provide meaningful `ariaLabel` values for custom nodes and edges;
- ensure custom node links and controls have visible focus states;
- provide text labels in addition to colors and line styles;
- provide a legend that explains node types, statuses, and edge meanings;
- provide an adjacent semantic table fallback with the same filtered records;
- preserve keyboard access to filters, fit-view controls, and linked detail pages;
- test at 200% zoom and in both light and dark themes.
Read-only means mutation is disabled, not navigation or focus.
## Performance
- Memoize node types outside React components.
- Build graph data with pure selectors and memoize by database revision and filter state.
- Filter before layout.
- Avoid recreating nodes and edges on every render.
- Do not render the full database by default.
- Use bounded dependency depth for large graphs.
- Lazy-load the BrowserOnly visualization component.
- Run asynchronous ELK layout only when the relevant graph input changes.
- Measure browser interaction with representative production-size data, not only two-node examples.
## Testing and verification
Test pure transformation and layout behavior first:
- scheduling dates produce stable Gantt `x`, width, and lane `y` values;
- Feature dependencies produce the expected directed edges;
- Requirement and architectural decision n:m mappings produce complete edges;
- filters remove nodes before layout;
- dangling IDs fail validation before graph construction;
- stable inputs produce byte-equivalent graph IDs and coordinates;
- fallback table rows represent the same relationships as graph edges;
- cycles follow the declared policy.
Then test React behavior:
- the BrowserOnly fallback renders during static generation;
- the canvas mounts with explicit width and height;
- nodes and edges are keyboard focusable;
- graph mutation is disabled;
- filters, detail links, fit view, pan, and zoom work;
- missing and empty datasets render useful states;
- table fallback remains usable without the graph.
Required delivery checks:
```text
pnpm data:validate
pnpm data:check
pnpm test
pnpm typecheck
pnpm build
```
After the production build, exercise each affected page in a real browser. Verify light and dark themes, desktop and narrow widths, keyboard navigation, labels, filters, links, pan/zoom, empty states, and the table fallback. Then verify the exact Gitea Actions SHA and deployed route.
## Acceptance checklist
- [ ] `@xyflow/react` and its base stylesheet are installed and imported.
- [ ] React Flow receives validated build-time data from source YAML through a pure adapter.
- [ ] Roadmap scheduling and Feature dependencies are stored in YAML, not TSX or MDX.
- [ ] Gantt-like positions are deterministic from dates and lanes.
- [ ] Dagre or ELK is chosen explicitly for dependency layout; React Flow is not described as providing automatic layout.
- [ ] Feature, requirement, and architectural decision relationships use stable IDs and typed nodes/edges.
- [ ] Docusaurus uses `BrowserOnly`, or the advanced React Flow 12 SSR dimensions are fully configured.
- [ ] The canvas has explicit width and height.
- [ ] Documentation views disable dragging, connecting, and deletion.
- [ ] Keyboard and screen-reader support remains enabled.
- [ ] Sharp styling applies `border-radius: 0` to nodes, controls, minimaps, filters, and surrounding panels.
- [ ] Every visualization has a semantic table fallback derived from the same adapter.
- [ ] Unit, component, type, production build, browser, Actions, and deployed-route checks pass.
@@ -0,0 +1,75 @@
# Reference-inspired documentation portals
Use this reference when a user selects an existing Docusaurus site as the visual and entry-page model for another documentation portal.
## Selection workflow
1. Inspect the exact URL the user selected; similarly named domains can present different products and layouts (for example, a documentation portal versus a community wiki).
2. Extract structural principles rather than cloning branded content:
- navigation hierarchy;
- hero composition;
- discovery groups;
- card/link density;
- typography posture;
- palette and surface relationships;
- responsive collapse behavior.
3. Map the target repository's real information architecture into those patterns. Do not invent fake metrics, features, or portfolio content.
4. Preserve explicit user constraints even when the reference violates them. A selected reference is a direction, not permission to override established design rules.
5. When presenting alternative references, track prior suggestions and exclusions. If the user requests “new options,” do not repeat any previously proposed option.
## Dedicated homepage pattern
When the docs plugin already owns `/`:
1. Move its introduction to a stable route such as `/overview/`.
2. Add `src/pages/index.tsx` and a CSS Module for the dedicated landing page.
3. Keep registry and detail routes unchanged.
4. Build with broken-link checking enabled and verify both `build/index.html` and `build/overview/index.html`.
## Sharp-rectangle adaptation
For a user who explicitly rejects rounded shapes, enforce the rule at both token and rendered-component levels:
```css
:root {
--ifm-global-radius: 0;
--ifm-code-border-radius: 0;
--ifm-pre-border-radius: 0;
--ifm-alert-border-radius: 0;
--ifm-button-border-radius: 0;
--ifm-badge-border-radius: 0;
--ifm-card-border-radius: 0;
--ifm-pagination-nav-border-radius: 0;
}
*, *::before, *::after {
border-radius: 0 !important;
}
```
The universal override is intentionally forceful. Use it only when the no-rounding requirement is global. Verify the minified production CSS contains the override, not just the source file.
## IOTA Docs-inspired posture
General principles observed from `https://docs.iota.org/` that transfer well to an original governance or ecosystem portal:
- dark technical canvas with one restrained teal accent;
- large editorial hero with a concise purpose statement;
- clear “Discover” groups that route users by task or domain;
- compact monospace eyebrow labels;
- bordered grid composition with low-elevation surfaces;
- content-rich cards whose lower rows act as direct navigation;
- a secondary section that explains the operating model or platform value;
- responsive collapse from multi-column grids to one column.
Do not reproduce IOTA names, claims, illustrations, or exact branded layout. Translate the hierarchy and visual posture to the target content.
## Verification
- Typecheck and production build pass.
- Root and relocated overview routes both exist.
- Distinctive landing-page markers appear in built HTML.
- Global shape rule appears in compiled CSS.
- Responsive grids collapse without horizontal overflow.
- Real CI completes successfully.
- Protected Pages deployments are verified through an authorized browser or the internal service proxy, with content markers rather than status alone.
@@ -0,0 +1,110 @@
# Research Address-Book Repositories
Use this pattern when a Gitea repository is intended to map a broad ecosystem, organization network, infrastructure landscape, or other changing domain.
## Design principle
Start with an **address book of authoritative directories and coordinating bodies**, not an attempted exhaustive list of every participant. Large ecosystems change too quickly for a hand-maintained flat inventory. Anchor later ingestion to official registries, public directories, and primary sources.
Separate four concerns:
1. Human navigation and concise summaries.
2. Canonical machine-readable records.
3. Method, taxonomy, and analytical findings.
4. Unverified leads and future research.
## Recommended structure
```text
.
├── README.md
├── address-book/
│ ├── README.md
│ └── <category>.md
├── data/
│ ├── README.md
│ └── entities.csv
├── docs/
│ ├── 01-scope-and-method.md
│ ├── 02-first-round-investigation.md
│ └── 03-taxonomy.md
├── research/
│ └── next-round.md
├── scripts/
│ └── validate.py
├── sources/
│ └── README.md
├── CONTRIBUTING.md
└── Makefile
```
Keep the top level small and obvious. Number only documents that have a reading sequence. Prefer category pages over one giant address-book document.
## Round-1 data model
CSV is a strong first format because it is Git-readable, spreadsheet-compatible, and dependency-free. Useful columns:
```text
id,name,category,scope,region,homepage,directory_url,role,source_url,status,verified_at,notes
```
Conventions:
- `id`: stable lowercase kebab-case; never recycle IDs.
- `category`: controlled taxonomy, based on function rather than assumed hierarchy.
- `scope`: distinguish global, regional, and regional-community scope.
- `region`: service or community coverage, not merely headquarters.
- `directory_url`: best public discovery, contact, membership, registry, or data entry point.
- `source_url`: primary evidence for the role claim.
- `status`: `verified`, `partial`, `lead`, or `stale`.
- `verified_at`: UTC date in `YYYY-MM-DD`.
- `notes`: caveats about automated access, completeness, ownership, or interpretation.
Move to versioned JSON/YAML records only when nested relationships, source merges, or repeated attributes make CSV inadequate. Do not add a database just for presentation.
## Research method
1. State the research question and explicit boundary.
2. Build a functional taxonomy; do not force decentralized ecosystems into a false parent/child hierarchy.
3. Identify globally or regionally authoritative directories and coordinating bodies.
4. Prefer official registries, APIs, standards bodies, and operator-maintained sources.
5. Verify URL reachability, but distinguish transport success from factual proof.
6. Put canonical entries in structured data; keep hypotheses in `research/`.
7. Write a first-round synthesis explaining findings, evidence limits, and missing coverage.
8. Define the next research rounds before adding automation.
## Provenance and contact safety
- Prefer public institutional and role-based contact points.
- Do not publish private personal data, credentials, or member-only incident contacts.
- A registry record does not necessarily establish current operator, owner, or beneficial controller.
- Community-maintained databases and measurement collectors expose partial viewpoints; document coverage caveats.
- A `403` from an automated client may be an access-policy caveat, not evidence that the official URL or entity is invalid. Record the caveat and use browser/manual verification when appropriate.
## Validation
Provide a standard-library-only validator where practical. Check:
- exact required columns;
- non-empty required fields;
- unique and well-formed IDs;
- controlled taxonomy/status values;
- HTTPS URLs;
- parseable, non-future verification dates;
- presence of required orientation documents.
Expose predictable commands:
```bash
make validate
make summary
```
Before push:
```bash
git diff --cached --check
make validate
```
After push, verify both the Git ref and at least one distinctive remote artifact using the authenticated Gitea API. A successful push message is useful evidence, but authenticated readback confirms the requested content rather than only the ref update.
@@ -0,0 +1,59 @@
# Research-led information architecture for evidence registries
Use this pattern when a Docusaurus site publishes a large evidence corpus organized around entities (for example clients) and derived records (for example systems).
## Guidance synthesis
Authoritative sources:
- GOV.UK content planning: begin with user needs and choose formats around tasks rather than mirroring the publisher's organization.
- GOV.UK writing guidance: use clear titles, summaries, headings, language, and descriptive links so users can find and understand content.
- W3C WAI design guidance: preserve clear hierarchy, meaningful controls, keyboard-visible focus, and multiple understandable ways to locate information.
- USWDS card guidance: cards should be coherent entry points within a collection, each covering one subject and leading to detail.
- USWDS table guidance: tables suit long, structured, comparable registries when headers and interpretation are explicit.
## Recommended portal model
1. Identify the dominant user tasks before editing navigation. For an entity/evidence registry these are usually:
- find an entity;
- follow a system/topic across entities;
- search names, aliases, contractors, and evidence text;
- understand methodology;
- verify or reuse source data.
2. Make the first three tasks the homepage's equal primary discovery paths. Keep methodology and provenance globally available but visually secondary.
3. Use stable global navigation labels based on destinations, such as `Overview`, `Clients`, `Systems`, `Methodology`, and `Data`. Avoid mixing a generated dossier sidebar destination with a duplicate directory destination in the same global navigation.
4. Keep large dossier sidebars available inside their docs plugin, but link global navigation to the registry/index page rather than opening an arbitrary first dossier.
5. Present scope before detail and label each metric precisely: authoritative entities, consolidated records, and preserved evidence observations are different measures.
6. Generate every changing count and overview table from canonical source data. Do not duplicate live totals in hand-maintained Markdown or JSX.
7. Explain table ordering, status vocabulary, count semantics, and safe interpretation immediately before long registry tables.
8. State explicitly that an association may mean ownership, use, procurement, operation, hosting, maintenance, support, or another relationship. Counts summarize public evidence, not a complete inventory.
9. Preserve direct provenance at detail level: every derived record must link observations back to canonical dossiers and evidence.
10. Record the reviewed guidance and resulting IA decisions in a repository research note so future redesigns can distinguish intentional structure from accidental layout.
## Docusaurus implementation notes
- Ensure both the top-level `title` and visible navbar title use the requested product name; changing only `themeConfig.navbar.title` leaves page titles inconsistent.
- For task-oriented global navigation, direct `to:` links to registry/index routes are often clearer than `docSidebar` items that enter a very large sidebar at its first document.
- Keep the navbar persistent when users frequently move among large registries.
- A homepage search card/button should focus the theme-owned navbar search input or dispatch its shortcut; never mount a second search component.
- Give button-based cards the same geometry, typography, hover state, and `:focus-visible` treatment as link cards.
- Use semantic `nav`, `section`, `aside`, headings, real links, and real buttons.
- Preserve square geometry when required by the site's design language.
## Verification
1. Regenerate source-derived content before validation.
2. Run repository validation, TypeScript checking, production build, and `git diff --check`.
3. Verify representative entity, derived-record, methodology, provenance, and search-index routes.
4. Inspect the homepage visually for hierarchy, alignment, clipping, overlap, and consistency.
5. Exercise the search trigger in a browser and inspect console/JavaScript errors.
6. Commit and push only after local checks pass; observe the exact Gitea Actions run through success.
7. Verify distinctive changed text through the deployed backend or an authorized browser. If public Pages is intentionally OAuth-protected, treat the expected redirect separately from backend deployment health.
## Pitfalls
- Do not present entity count, unique-record count, and observation count as interchangeable.
- Do not make four or five homepage cards compete equally when only three are primary discovery tasks.
- Do not hard-code generated overview/status counts in Markdown.
- Do not infer that a successful Actions run alone proves the served content changed; verify a distinctive marker.
- Do not diagnose an OAuth redirect as a Docusaurus deployment failure.
+276
View File
@@ -0,0 +1,276 @@
# Writing Guide
Voice, tone, and language standards for documentation.
---
## Voice Principles
### Be Direct
Not: "It should be noted that the configuration file needs to be updated."
Yes: "Update the configuration file."
Not: "Users may find it helpful to restart the server."
Yes: "Restart the server."
### Address the Reader
Not: "Developers can use the API to..."
Yes: "Use the API to..."
Not: "One might consider..."
Yes: "Consider..."
### Present Tense, Active Voice
Not: "The file will be created when..."
Yes: "The file is created when..."
Not: "The request is processed by the server."
Yes: "The server processes the request."
---
## Words That Weaken
Remove these. They add nothing:
| Remove | Why |
|--------|-----|
| simply, just, easily | Implies difficulty is your fault |
| obviously, clearly | If it were obvious, you wouldn't write it |
| please | Unnecessary in instructions |
| in order to | "To" works |
| it is important to note that | Just state it |
| basically | Delete |
| actually | Delete |
**Before:** "Simply run the following command to easily set up..."
**After:** "Run the command to set up..."
---
## Inclusive Language
### Required Replacements
| Don't Use | Use Instead |
|-----------|-------------|
| whitelist / blacklist | allowlist / blocklist |
| master / slave | primary / replica, main / secondary |
| master branch | main branch |
| sanity check | confidence check, validation, smoke test |
| dummy (value/data) | placeholder, sample, test |
| cripple | disable, impair, limit |
| blind to | unaware of, ignoring |
| crazy, insane | unexpected, surprising, intense |
| man-hours | person-hours, engineering hours |
| manpower | workforce, staffing |
| guys | folks, everyone, team, y'all |
| he/she, his/her | they, their |
| normal users | typical users, most users |
### Writing About People
**Disability:**
- Not: "suffers from," "victim of," "wheelchair-bound"
- Yes: "has," "uses a wheelchair," "with [condition]"
**Age:**
- Not: "the elderly," "seniors"
- Yes: "older adults"
**Experience level:**
- Not: "for dummies," "idiot's guide"
- Yes: "introduction," "getting started"
### Cultural Sensitivity
- Avoid US-centric examples (Thanksgiving, Super Bowl, Fahrenheit)
- Use ISO date format: 2024-01-15, not 01/15/2024
- Use 24-hour time or specify timezone
- Don't assume everyone celebrates the same holidays
---
## Second Person "You"
Always address the reader directly:
Not: "The user should configure their settings."
Yes: "Configure your settings."
Not: "Developers will need to install..."
Yes: "Install..."
**When to use "we":**
- Only when the reader is genuinely working WITH you
- "We'll build this together" (in a collaborative tutorial)
- Never use "we" to mean "our company"
---
## Code in Prose
### Commands
Use backticks and be specific:
Not: "Run the build command."
Yes: "Run `npm run build`."
### Filenames and Paths
Always in backticks: `docusaurus.config.ts`, `src/pages/`
### Configuration Values
In backticks: Set `trailingSlash` to `false`.
### Placeholders
Use angle brackets and ALL_CAPS:
```bash
git clone https://github.com/<YOUR_USERNAME>/<REPO_NAME>
```
Not `{username}` or `$USERNAME` in documentation prose.
---
## Headings
### Capitalization
Use sentence case, not title case:
Not: "How To Configure Your Build Settings"
Yes: "How to configure your build settings"
### Parallel Structure
If one heading starts with a verb, they all should:
**Good:**
- Install dependencies
- Configure the project
- Deploy to production
**Bad:**
- Installation
- Configure the project
- Deploying to production
### Depth
Never go past H3 in a single page. If you need H4, the page is too complex—split it.
---
## Lists
### When to Use Bullets vs. Numbers
**Numbers:** When order matters (steps, priority)
**Bullets:** When order doesn't matter (features, options)
### List Formatting
Each item should be grammatically parallel:
**Good:**
- Configure the database
- Set up authentication
- Deploy the application
**Bad:**
- Database configuration
- Set up authentication
- The application should be deployed
### Sentence Fragments OK
In lists, sentence fragments are fine:
- Fast builds
- Hot reloading
- TypeScript support
---
## Examples
### Show, Don't Tell
Not: "The configuration accepts various options for customization."
Yes:
```javascript
{
theme: 'dark',
language: 'en',
}
```
### Minimal, Then Complete
Show the simplest working example first:
```javascript
// Minimal
{ name: 'my-project' }
```
Then show comprehensive:
```javascript
// With all options
{
name: 'my-project',
version: '1.0.0',
// ...
}
```
### Comments in Code
Use comments to explain WHY, not WHAT:
```javascript
// Good: Explains reasoning
timeout: 30000, // Matches API's default request timeout
// Bad: States the obvious
timeout: 30000, // Set timeout to 30000
```
---
## Error Messages
When documenting errors:
1. Show the exact error message the user sees
2. Explain what caused it
3. Give the fix
```
Error: Cannot find module 'react'
```
**Cause:** Dependencies aren't installed.
**Fix:** Run `npm install` in your project directory.
---
## Checklist Before Publishing
- [ ] Title describes what the reader will DO
- [ ] Description is under 160 characters
- [ ] No "simple," "easy," "just," or "obviously"
- [ ] All code examples tested and working
- [ ] Links to related pages (no dead ends)
- [ ] Heading structure: H1 → H2 → H3 only
- [ ] Inclusive language throughout