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
+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