Skip to content

Configuration of MkDoc

This document describes the complete MkDocs configuration for the Scanframe Wiki documentation service, including Docker setup, PlantUML rendering, PWA manifest, CPU optimization, and custom styling.

Project Structure

The documentation project is organized across two main directories:

Directory Purpose Container Mount
mkdoc/ MkDocs configuration, Dockerfile, theme overrides, and static assets /docs (read-only)
docroot/ Markdown content files (wiki, progress, miscellaneous) /src (read-only)

The docroot/ directory contains symlinks or subdirectories for each content section:

  • docroot/wiki/ — Wiki documentation pages.
  • docroot/progress/ — Progress tracking pages.
  • docroot/misc/ — Miscellaneous content.
  • docroot/website/ — Production website content.

Docker Service

Dockerfile (mkdoc/Dockerfile)

The Dockerfile extends the squidfunk/mkdocs-material base image and adds:

  1. Java runtime and Graphviz — Required for local PlantUML rendering.
  2. PlantUML JAR — Downloaded to /opt/plantuml/plantuml.jar for offline diagram rendering.
  3. Python packages — mkdocs-awesome-pages-plugin, mkdocs-glightbox, mkdocs-build-plantuml-plugin, and plantuml-markdown.
  4. CPU optimization — The livereload polling interval is patched from 0.5 seconds to 600 seconds (10 minutes) to eliminate idle CPU usage.

Docker Compose (mkdoc/docker-compose.yml)

The service is defined in mkdoc/docker-compose.yml:

  • Image: Built from the local Dockerfile.
  • Port mapping: 127.0.0.1:8081 maps to container port 8000.
  • Volumes: Two read-only mounts:
    • ../mkdoc → /docs (MkDocs configuration and static assets).
    • ../docroot → /src (Markdown content directories).
  • Restart policy: unless-stopped ensures the service restarts on container restart.
  • User: Runs as www-data (UID 33:33).

Starting the Service

Use the build.sh script from the project root:

Command Description
build Build the Docker image and start the service.
up Start the service in the background.
down Stop and remove the container.
restart Restart the running container.
status Show whether the container is running.
info Show Docker image information.
prune Remove unused Docker resources.
exec Execute a command inside the container.

The site is available at http://127.0.0.1:8081.

Build Management Script (mkdoc/build.sh)

A symlinked script build.sh in the project root provides all Docker Compose commands for the MkDocs service. The actual script resides in mkdoc/build.sh and uses two directory variables:

  • script_dir — Resolves to the actual script location (mkdoc/), used for finding docker-compose.yml.
  • run_dir — Resolves to the directory from which the script was invoked (the symlink's location, i.e., the project root).

Available Commands

Command Description
build Build the Docker image and start the service.
restart Restart the running container, triggering a full rebuild.
up Start the service in the background.
down Stop and remove the container.
status Show whether the container is running and display stats using jq.
info Show information about the Docker image.
prune Remove unused Docker resources.
exec Execute a command inside the running container, passing remaining args.
auto-restart Watch documentation files and auto-restart the container on changes.
help Show the help message.

Status Command

The status command uses jq to parse the NDJSON output from docker compose ps --format json, extracting the .State field to determine if the container is running.

Auto-Restart Command

The auto-restart command provides event-driven file watching with near-zero CPU usage as an alternative to manual rebuilds:

  1. Builds watch list — Collects real paths (via readlink -f) of all entries in ${script_dir}/docs and the mkdocs.yml configuration file.
  2. Watches for changes — Uses inotifywait to monitor for modify, create, delete, and move events across all watched paths.
  3. Debounces — Waits 7 seconds after the last change before triggering a restart, so rapid saves trigger only one rebuild.
  4. Restarts container — Executes docker compose restart to rebuild all pages.
./build.sh auto-restart

Requires inotify-tools package installed. The command runs in the foreground and can be stopped with Ctrl+C.

Manual Rebuilds

Due to the CPU optimization that disables file polling, automatic live reload is not available by default. Instead, use one of:

  • ./build.sh restart — Restarts the container, forcing mkdocs serve to rebuild all pages from scratch on startup.
  • ./build.sh auto-restart — Event-driven watching with automatic restarts and near-zero CPU usage.
  • ./rebuild.sh — Legacy script that restarts the container via docker compose restart.

MkDocs Configuration (mkdoc/mkdocs.yml)

Site Metadata

Key Value Purpose
site_name Scanframe Wiki Browser tab title and homepage heading.
site_description Public Wiki of Scanframe SEO and social media preview text.
site_url https://wiki.scanframe.com Base URL for absolute links and sitemap.
dev_addr 0.0.0.0:8000 Binds to all interfaces so Docker port mapping works.

File Watching (Disabled)

The watch section is commented out to avoid high CPU usage from continuous file polling. Manual rebuilds via rebuild.sh are used instead.

Theme Configuration

The site uses Material for MkDocs with the following settings:

  • Custom directory: overrides — Contains template overrides (e.g., the PWA manifest link).
  • Favicon: assets/scanframe.ico.
  • Logo: assets/scanframe.png.

Theme Features

Feature Purpose
content.code.copy Copy button on code blocks.
search.suggest Autocomplete suggestions in the search box.
search.highlight Highlights search matches on result pages.
search.share Shareable search result URLs.
navigation.indexes Allows landing pages for sections (index.md as section default).
content.code.annotate Code block annotations via HTML comments.
content.tabs.link Syncs code tab selections across pages in a session.

Color Palette

Two palettes are defined based on the user's OS-level color scheme preference:

  • Light mode: default scheme with indigo primary and slate accent colors.
  • Dark mode: slate scheme with indigo primary and slate accent colors.

Each palette includes a toggle button to switch to the other scheme.

Plugins

Plugin Purpose
search Built-in search index generation for the site search feature.
awesome-pages Auto-generates navigation from directory structure using _navigate_.yml files. Sections with a single page are not collapsed.
glightbox Lightbox gallery for images — click to view full-size with navigation.

The awesome-pages plugin uses .pages.yml to define the navigation order:

nav:
    - 🌐 Website: website
    - 📢 Introduction: index.md
    - 📙 Wiki: wiki
    - 📈 Progress: progress
    - 📦 Miscellaneous: misc

Each entry maps a display title (with emoji prefix) to a content directory or file. Subdirectories are auto-discovered unless explicitly overridden with a .pages file in the target subdirectory.

Renaming Subdirectories

To rename a subdirectory in the navigation, create a .pages file in the target subdirectory with a title: field. For example, docroot/wiki/Configuration/.pages contains:

title: ⚙️ Configuration

This overrides the auto-discovered directory name in the navigation sidebar.

Emoji Title Prefixes

All Markdown documents in the content directories have emoji prefixes in their title: frontmatter field:

  • docroot/progress/ — All 47 files prefixed with 🔵.
  • docroot/wiki/ — All 146 files prefixed with 🟠.

Extra Configuration

  • generator: false — Hides the "Powered by MkDocs" footer line.

Custom Stylesheets

  • stylesheets/extra.css — Custom CSS that overrides theme defaults for font sizes, spacing, colors, and layout adjustments.

Custom JavaScript

File Purpose
javascripts/external-links.js Adds external link icons and target="_blank" to outbound links.
javascripts/hide-elements.js Hides header, navigation sidebar, and footer on the production domain (www.scanframe.com) when the path starts with /website.

Markdown Extensions

Extension Purpose
attr_list Allows HTML attributes on Markdown elements (e.g., class, id).
md_in_html Renders Markdown inside HTML blocks (e.g., <div> with Markdown content).
pymdownx.highlight Syntax highlighting for fenced code blocks (Pygments-based) with line number anchors and custom CSS classes per language.
pymdownx.inlinehilite Inline code highlighting with backticks and a language (e.g., `code { .python }).
pymdownx.snippets Includes external files via {{ !path/to/file }} syntax.
pymdownx.superfences Extended fenced code blocks with highlighting, line numbers, and titles.
pymdownx.tabbed Tabbed content blocks for showing multiple code variants (alternate CSS style).

PlantUML Diagram Rendering

Overview

PlantUML diagrams are rendered offline using a local Java-based PlantUML JAR installed in the Docker container. No external network access is required.

Configuration

The plantuml_markdown extension is configured in mkdocs.yml:

- plantuml_markdown:
plantuml_cmd: 'java -Dplantuml.include.path=/src/progress:/src/wiki:/src/misc -jar /opt/plantuml/plantuml.jar'
format: 'svg'
  • plantuml_cmd: Command to invoke PlantUML locally. The -Dplantuml.include.path JVM argument tells PlantUML where to resolve !include directives. The paths match the Docker volume mounts.
  • format: Output format is svg (recommended for scalability).

Supported Patterns

Two PlantUML patterns are supported:

  1. Inline diagrams — Diagram code written directly in a fenced code block:

    <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGRhdGEtZGlhZ3JhbS10eXBlPSJTRVFVRU5DRSIgc3R5bGU9IndpZHRoOjExN3B4O2hlaWdodDoxMzJweDtiYWNrZ3JvdW5kOiNGRkZGRkY7IiB3aWR0aD0iMTE3cHgiIGhlaWdodD0iMTMycHgiIHZpZXdCb3g9IjAgMCAxMTcgMTMyIiB6b29tQW5kUGFuPSJtYWduaWZ5IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIiBjb250ZW50U3R5bGVUeXBlPSJ0ZXh0L2NzcyI+PD9wbGFudHVtbCAxLjIwMjYuOT8+PGRlZnMvPjxnIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciPjxnIGNsYXNzPSJwYXJ0aWNpcGFudC1saWZlbGluZSIgZGF0YS1xdWFsaWZpZWQtbmFtZT0iQWxpY2UiIGRhdGEtZW50aXR5LXVpZD0icGFydDEiIGlkPSJwYXJ0MS1saWZlbGluZSIgZGF0YS1zb3VyY2UtbGluZT0iMiI+PGc+PHRpdGxlPkFsaWNlPC90aXRsZT48cmVjdCB4PSIyOC41MTUiIHk9IjQxLjgiIHdpZHRoPSI4IiBoZWlnaHQ9IjQ3LjYiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iMCIvPjxsaW5lIHgxPSIzMi4wMTUiIHkxPSI0MS44IiB4Mj0iMzIuMDE1IiB5Mj0iODkuNCIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDowLjU7c3Ryb2tlLWRhc2hhcnJheTo1LDU7Ii8+PC9nPjwvZz48ZyBjbGFzcz0icGFydGljaXBhbnQtbGlmZWxpbmUiIGRhdGEtcXVhbGlmaWVkLW5hbWU9IkJvYiIgZGF0YS1lbnRpdHktdWlkPSJwYXJ0MiIgaWQ9InBhcnQyLWxpZmVsaW5lIiBkYXRhLXNvdXJjZS1saW5lPSIyIj48Zz48dGl0bGU+Qm9iPC90aXRsZT48cmVjdCB4PSI4My4zNTEiIHk9IjQxLjgiIHdpZHRoPSI4IiBoZWlnaHQ9IjQ3LjYiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iMCIvPjxsaW5lIHgxPSI4Ni44NTEiIHkxPSI0MS44IiB4Mj0iODYuODUxIiB5Mj0iODkuNCIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDowLjU7c3Ryb2tlLWRhc2hhcnJheTo1LDU7Ii8+PC9nPjwvZz48ZyBjbGFzcz0icGFydGljaXBhbnQgcGFydGljaXBhbnQtaGVhZCIgZGF0YS1xdWFsaWZpZWQtbmFtZT0iQWxpY2UiIGRhdGEtZW50aXR5LXVpZD0icGFydDEiIGlkPSJwYXJ0MS1oZWFkIiBkYXRhLXNvdXJjZS1saW5lPSIyIj48cmVjdCB4PSIxMCIgeT0iMTAiIHdpZHRoPSI0NC4wMyIgaGVpZ2h0PSIzMC44IiBmaWxsPSIjRTJFMkYwIiBzdHlsZT0ic3Ryb2tlOiMxODE4MTg7c3Ryb2tlLXdpZHRoOjAuNTsiIHJ4PSIyLjUiIHJ5PSIyLjUiLz48dGV4dCB4PSIxNyIgeT0iMzAuNzYyIiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjE0IiB0ZXh0TGVuZ3RoPSIzMC4wMyI+QWxpY2U8L3RleHQ+PC9nPjxnIGNsYXNzPSJwYXJ0aWNpcGFudCBwYXJ0aWNpcGFudC1oZWFkIiBkYXRhLXF1YWxpZmllZC1uYW1lPSJCb2IiIGRhdGEtZW50aXR5LXVpZD0icGFydDIiIGlkPSJwYXJ0Mi1oZWFkIiBkYXRhLXNvdXJjZS1saW5lPSIyIj48cmVjdCB4PSI2Ny40ODIiIHk9IjEwIiB3aWR0aD0iMzguNzM4IiBoZWlnaHQ9IjMwLjgiIGZpbGw9IiNFMkUyRjAiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgcng9IjIuNSIgcnk9IjIuNSIvPjx0ZXh0IHg9Ijc0LjQ4MiIgeT0iMzAuNzYyIiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjE0IiB0ZXh0TGVuZ3RoPSIyNC43MzgiPkJvYjwvdGV4dD48L2c+PGcgY2xhc3M9InBhcnRpY2lwYW50IHBhcnRpY2lwYW50LXRhaWwiIGRhdGEtcXVhbGlmaWVkLW5hbWU9IkFsaWNlIiBkYXRhLWVudGl0eS11aWQ9InBhcnQxIiBpZD0icGFydDEtdGFpbCIgZGF0YS1zb3VyY2UtbGluZT0iMiI+PHJlY3QgeD0iMTAiIHk9Ijg5LjQiIHdpZHRoPSI0NC4wMyIgaGVpZ2h0PSIzMC44IiBmaWxsPSIjRTJFMkYwIiBzdHlsZT0ic3Ryb2tlOiMxODE4MTg7c3Ryb2tlLXdpZHRoOjAuNTsiIHJ4PSIyLjUiIHJ5PSIyLjUiLz48dGV4dCB4PSIxNyIgeT0iMTEwLjE2MiIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxNCIgdGV4dExlbmd0aD0iMzAuMDMiPkFsaWNlPC90ZXh0PjwvZz48ZyBjbGFzcz0icGFydGljaXBhbnQgcGFydGljaXBhbnQtdGFpbCIgZGF0YS1xdWFsaWZpZWQtbmFtZT0iQm9iIiBkYXRhLWVudGl0eS11aWQ9InBhcnQyIiBpZD0icGFydDItdGFpbCIgZGF0YS1zb3VyY2UtbGluZT0iMiI+PHJlY3QgeD0iNjcuNDgyIiB5PSI4OS40IiB3aWR0aD0iMzguNzM4IiBoZWlnaHQ9IjMwLjgiIGZpbGw9IiNFMkUyRjAiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgcng9IjIuNSIgcnk9IjIuNSIvPjx0ZXh0IHg9Ijc0LjQ4MiIgeT0iMTEwLjE2MiIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxNCIgdGV4dExlbmd0aD0iMjQuNzM4Ij5Cb2I8L3RleHQ+PC9nPjxnIGNsYXNzPSJtZXNzYWdlIiBkYXRhLWVudGl0eS0xPSJwYXJ0MSIgZGF0YS1lbnRpdHktMj0icGFydDIiIGlkPSJtc2cxIiBkYXRhLXNvdXJjZS1saW5lPSIyIj48cG9seWdvbiBwb2ludHM9Ijc0Ljg1MSw2Ny40LDg0Ljg1MSw3MS40LDc0Ljg1MSw3NS40LDc4Ljg1MSw3MS40IiBmaWxsPSIjMTgxODE4IiBzdHlsZT0ic3Ryb2tlOiMxODE4MTg7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjEwOyIvPjxsaW5lIHgxPSIzMi4wMTUiIHkxPSI3MS40IiB4Mj0iODAuODUxIiB5Mj0iNzEuNCIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IHg9IjM5LjAxNSIgeT0iNjYuNTc5IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEzIiB0ZXh0TGVuZ3RoPSIzMC44MzYiPkhlbGxvPC90ZXh0PjwvZz48P3BsYW50dW1sLXNyYyBTeXA5SjR2THFCTEpTQ2ZGaWJCbUlDdDlvVVRJVzA4a0JhQ3o4bUNaQ3ByQjAwMDA/PjwvZz48L3N2Zz4=" class="uml" alt="uml diagram" title="" />
    
  2. Include diagrams — Reference to an external .puml file:

    <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHN0eWxlPSJ3aWR0aDo1NDBweDtoZWlnaHQ6MzM0cHg7YmFja2dyb3VuZDojRkZGRkZGOyIgd2lkdGg9IjU0MHB4IiBoZWlnaHQ9IjMzNHB4IiB2aWV3Qm94PSIwIDAgNTQwIDMzNCIgem9vbUFuZFBhbj0ibWFnbmlmeSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSIgY29udGVudFN0eWxlVHlwZT0idGV4dC9jc3MiPjw/cGxhbnR1bWwgMS4yMDI2Ljk/PjxkZWZzLz48ZyBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIj48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iNTM5LjE1MSIgaGVpZ2h0PSIyMTEuNjAxIiBmaWxsPSIjRkZGIiBzdHlsZT0ic3Ryb2tlOiNGRkY7c3Ryb2tlLXdpZHRoOjE7Ii8+PHRleHQgeD0iNSIgeT0iMTYuNzk2IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEyIiB0ZXh0TGVuZ3RoPSIxMjkuMjQ3IiBmb250LXdlaWdodD0iNzAwIj5XZWxjb21lIHRvIFBsYW50VU1MITwvdGV4dD48dGV4dCB4PSI1IiB5PSIzMS4xOTYiIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiPsKgPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjQ1LjU5NiIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiIgdGV4dExlbmd0aD0iMjQyLjIxIj5Zb3UgY2FuIHN0YXJ0IHdpdGggYSBzaW1wbGUgVU1MIERpYWdyYW0gbGlrZTo8L3RleHQ+PHRleHQgeD0iNSIgeT0iNTkuOTk2IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEyIj7CoDwvdGV4dD48dGV4dCB4PSI1IiB5PSI3NC4zOTYiIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjkzLjE2OSIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSI+Qm9iLT5BbGljZTrCoEhlbGxvPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9Ijg4Ljc5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiI+wqA8L3RleHQ+PHRleHQgeD0iNSIgeT0iMTAzLjE5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiIgdGV4dExlbmd0aD0iMTMuNjQ0Ij5PcjwvdGV4dD48dGV4dCB4PSI1IiB5PSIxMTcuNTk3IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEyIj7CoDwvdGV4dD48dGV4dCB4PSI1IiB5PSIxMzEuOTk3IiBmaWxsPSIjMDAwIiBmb250LXNpemU9IjEyIiB0ZXh0TGVuZ3RoPSI3NC43OTciIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiPmNsYXNzwqBFeGFtcGxlPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjE0Ni4zOTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiPsKgPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjE2MC43OTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjMwMy4xOTQiPllvdSB3aWxsIGZpbmQgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCBQbGFudFVNTCBzeW50YXggb248L3RleHQ+PHRleHQgeD0iMzEwLjgzNCIgeT0iMTYwLjc5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiIgdGV4dExlbmd0aD0iMTEzLjMxNyIgdGV4dC1kZWNvcmF0aW9uPSJ1bmRlcmxpbmUiPmh0dHBzOi8vcGxhbnR1bWwuY29tPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjE3NS4xOTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiPsKgPC90ZXh0Pjx0ZXh0IHg9IjUiIHk9IjE4OS41OTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjkyLjIzMyI+KERldGFpbHMgYnkgdHlwaW5nPC90ZXh0Pjx0ZXh0IHg9Ijk5Ljg3MyIgeT0iMTg5LjU5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiIgdGV4dExlbmd0aD0iMzYuOTM2IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIj5saWNlbnNlPC90ZXh0Pjx0ZXh0IHg9IjEzOS40NDkiIHk9IjE4OS41OTciIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjQ5LjI0OCI+a2V5d29yZCk8L3RleHQ+PHRleHQgeD0iNSIgeT0iMjAzLjk5NyIgZmlsbD0iIzAwMCIgZm9udC1zaXplPSIxMiI+wqA8L3RleHQ+PGltYWdlIHdpZHRoPSI4MCIgaGVpZ2h0PSI3MSIgeD0iNDUzLjE1MSIgeT0iNiIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFGQUFBQUJIQ0FNQUFBQ25IREM4QUFBREFGQk1WRVgvLy8rQnBidkQzdS9mN3ZmUHo4K256K2cza3N1WkVEa1dBRHIvdWpsdnNkcFRvZExDd3NPTHdPRWJnOFQvL3Y2cHFhbVpFVG5MeXN2VDA5VG81K2pxTFM0VkFUajkvZjI3dTd2RHc4TVRmc0srdjcvajQrU093ZUpwcmRqTDQvS3hzYkVyaThqazhQZ1hhNTdjMjl1VUVUZXoxdXRRb05KMXROdEVtYzljcHRXQnV0NmF5T1cvM082WUVEalk2dlVmaGNYQndjSHM3T3p3OS92ODN0Nzl1VG43Ky92WDE5ZWhvYUh4OGZHMnRMWEh4OGZ1Vmxmck5UWWFCam4vdlVINStmbWtvNldYbDVmLytmbjk1dWIrOVBUKzhQRDNxS2lkbWFNeElVcm5xRFdjRmo3bjVlWDA5UFR1N3UyU2twS2ZuWjZycTZ6MGxKVG00dHpKckhVZkRUMzM5L2VGR0RuNnRqY3JFek92cmJCdVozbDRlb21TTVM3SHo5U21wcWFzdThTRmlZdVFFVGZhMmR1MW02T09NMDUxSjBOV1RXTDV4c2U0dUxqNnpjMzNyNi80dmI3eWRIWHFMaS83MmRudFRFM3RSMGlqYm41alczQyt1OE5BTlZRckcwWEZ1YnFSVkdaemE0R1FHVDJWYVM3YTBOUEp0STZJSmovenN6cmZxa1hYenIvSHc4Y2dDMExVcWx0T1EySnNTQzViVEdqT3BGYWZTaTIraURHdHl0MTRtN1RKaFRDUEpVU3dvSUcvcWEraWtIaUlncENSSGpJOWo4S1hibmFRSDBGTGY2RkdQbHAwY25ZeUtFMnJnNDlXajdkRlhIbWN3dHJwNnV1K3hNanZZV0x2VzF6OTdlM3NQRDNzUDBEdVVsUDcwOVBZemREUG16cVpXV3lTUlZ5Z2ZISGQxc21Xa1orUGduaWJFVHFzbDI2MHE1Zkx1cHBKTEMvZHBqMkhQRkxUdEh2L3VqclR3Snk1czZpV2pJaTBpRDNqb1RUQ21VM3RyanV2WlMyemxGdmpxMEhTeUxiWXAwcVJ0czEwWEd4RUtDOXlQVkJRWklPUGRsTnZNazJOZVlOcVJsdWp0Y0NWcnIzajVlWTFnSzlRbjlHTHA3bGJlSlhSeHJDa2laRGFvenl1a29KK0gwQjNyYytPWXkraXBySmtZWGRobXI2ZWUwYWZzNzl4cTlCaWtyR0tqSTNGb21FaUxGTzBheTZuZkltNHBaWFJqVEZ1aFpTWldtMHpjWnlKTjBLTlRrNGVlYk5mZElHR2xaOTNoNWtjRUQ1bk9WWXlSV2J6aUlrOWRKNGtlckU1Wll4UlVuTEcxZDdwOFBUdlkyUHRUay8xbloxeWNWL2JBQUFGeDBsRVFWUjRYdTFZZTFCVVZSZy9lM2RobDRjc0t5eHFQbEJKUVZFVEYyaWNBWTEzR29na0VGcWl6cVJScG9CcFRDSWFwYVVPaFdOalVkT01TQnBxSllaVGc0S2lnRktBRHpTSVZaNHFENVZGbHVYaExndmJQZWUrcjQ5Ujd2MnozK3pjYzc3Zk9mUGI3enZmUGQ4NXV3Q0lod1Q0d1Bpc0FOd1BBdUlLSG5iSEgxSStLd0FYREExOFNoRFc1OENuZUNFblhsOEpHd21mSHlsQ1BTdmxaL0ZXeGg4WUlSSXhUV2taQUVvZmtUejhzTnE3dEFxRTJnT0RPQjZHWVpvYlZhSDIwU1ZaNG9RY2RtWFYwTDdjNGI4T0Z2SkhSb1l3ZFZJb3lQWDNneHRGREErVjhlNm1NOHBCb0NqaWo0d1FpV28vQUx6Vi9rcGtpZkJpMTNwNHdhWnVHckpFRUN5Y2l4Y0VWU2JZZ0Z3VW96ZzQvcVNyYU94clA3TzhFb2dqMkhEeHlNV216cGs2ODIzK3lJamh2UjdmTE9wRHNDdUdod0MwV2Zia2pRcThBME1XQzBFZ0lZelBDWU15bU04SXdjdno2SzRJV3c4c2tUcmdxM2lQTUlRbnhkZktydnllZHB5dmJTc3lCUXQ2emNINk94WG1qcVl4ZzlQYmdYQkJ2ZUs2YVJBWE5FbDFTc2MraHo3QmdudHlvaGIvZ2dRbE5ycmR6Zkl1b1lLU2I3OTA2N1ByUm9MbUdvMjVTZWdoSlJuMXlNclNiUFRwdFphb0Jsd3RsV2JCSHNweGlXK21XOCs0NmVyZVZuTVhHeGJGUSttam9maUhaVHJjbEpuNUUxNFVFZ2RyTzl0UlZwaDBIREpsWWxSc0x2NFhGQTdSQkExa0s1b2dCYjdndG4wOEF2eFd5bWMyYitZekxIQzNubk9DTkh2ZGF5VXNacG5ud25QcUdmK3lHS2NQS2x2OTJ2dEpDMjA5ekd5UldKbWd5ZDE2c1NHR1QvQW1NMnJkS1pLWkh4d0N6N0tVb2RMekpPTzh0aDRPQnF4ZG8wYzJ0ZlVrTnIzUWxKa1pRV1dNQlAwdXdCSHZtUVFiM3pSVkNNbnNNTzZBVGF6ZjNmMGtremEwSFRhUENkSWhweVRrbnFQNjFUZTFlVjJnUnByMUZVbjR4dHhSVDcycERIaGowOS9VbkJMaml2NzJwNGQ4RkV0bExpYkJkN1FnMHhLNStYZlM5cTJBenhTVkhQbk5JTUJTOEJRUHc4TjFXNGdsUVdqMG10cDQrblNIaHZLWU9NNEdMNnZIMWpHVGNEUTNwMTk3OUFRUEk1YWdYRURZd3VTNWF3a25KNzNySFU1NlJ5TEFyNkFDcE81aU1SR3ZwM1h5a2pJbWdzNEZnbTIvN1VSYzhkaGJlRDlqZGhZVk5vbjBtZEZjQWpUSVhibUM3NVZybVZIY1ErSkRJWGJ1Tm1ZVVlsWm9ZeDZYY1dqS1RXYXZZWGQxV3pVOU9FaDl3TEZmRVZGemE1UEVoVmhEQXZmTHg2MGNXTTNlUE1hOWh5Wkc1RE5yNkRiUWlDVWNZVTBBS0d6R3lZeGxrZit3eG5Dayt3ZHdDYkEvVGtXSDdEYmdvcThkY21LTnNzUVEzRi9KbU14bElqL2Z6by83czlPSXdVTWViYll6YmgyekxmazdlaEJHeklhdTZPZU8reHpHS2xCUndDR0FYTmFDRmdZYlJvS0craE1UZk1mZjRNNmgwTFl4cG12LzkyeW05dEl4YzR1Unphd3luQ0VXR2h1bTdvZGY5Lzc1dmg4emcwYktpalg1QU14aE1hY3NLOHBCeGFKaWh0blFxcjFLRzh5Rk16cS9kdXRKMmlLd05DYlhBN1lhbXNtMlhuMFh0Z3Rvd1kzWHk4cnBZYzROTnFvbjhlTTQ5aytOdHpVNTVHdE1sZUdBa05SNm9sZERNaEgrZTlFWDBKQitVWUt2NFdnTFpyTENqSFY3d255WWx6Slp2WVY2QTZkMXc2eEVwaDg0MlVVeVRrakdJYnp3UkEvSklPQkpLU3Q4OHlnbHFORDFoNlRwaVFxUVY5OXdtSjdua25RY2dJSUhxZDAwRXdTemd1ZGlnR1lRVUhFNHFGTWNHSmIyMmtwN2xOb3A4cDZ6blN0dmc1UzJlMVRWUnRpNTZ3L3pqOGZaektMaURhMzE3TXFCUUZicytKM2RjYlNnbytINEQwa0xPb2tKODIvTlI4S1picDllSVpnbGx3S1I4RWMxZWpJWDgyN1BPay8wNkhwWWZmR2Q1ZGtvWkpYTXFEQVZYZDFOYmhhdmx4NE95dUFpUGN5QUYzSWNrOGZhWEF1RTVXVG9BcGtNeHlrTDFQYk5SSjkxU0RrWE95eW1QRlIxVU1sdzlHalJQS0NyUHNSU3ZVbmJ6YmtFTGoyUFNTUmtRTGlIMUF2Uk9UdXRscHJ5TEhRQW9DRCtDMkJCUDRucE13ZDl6c0lnemdhRHNLK2JjdG1WdzNoZXRhaTV0OVJTNFBFcXViNFFuQ3Z4dGVHek9aeVFYeGpzY3hraEhpc1dLTWk3MitUMHAxZHhHZUdZa0cwZXorZWVHN0luL24vb05PZXhDOWZ6SWlMdlB4VnRLcDg0aXphK0FBQUFBRWxGVGtTdVFtQ0MiLz48cmVjdCB4PSIwIiB5PSIyMTEuNjAxIiB3aWR0aD0iNTM5LjE1MSIgaGVpZ2h0PSIxMjEuNCIgZmlsbD0iIzAwMCIgc3R5bGU9InN0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IHg9IjUiIHk9IjIyOC42MDEiIGZpbGw9IiMzM0ZGMDIiIGZvbnQtc2l6ZT0iMTIiIHRleHRMZW5ndGg9IjM2MS41OCIgZm9udC13ZWlnaHQ9IjcwMCIgZm9udC1zdHlsZT0iaXRhbGljIj5QbGFudFVNTCB2ZXJzaW9uIDEuMjAyNi45IC8gMThkMGFhZSBbMjAyNi0xMC0xNyAwODoyNToxMCBVVENdPC90ZXh0PjxyZWN0IHg9IjUiIHk9IjIzOS4wMDIiIHdpZHRoPSIxNDAuNjEiIGhlaWdodD0iMjEuOCIgZmlsbD0iIzMzRkYwMiIgc3R5bGU9InN0cm9rZTojMzNGRjAyO3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IHg9IjYiIHk9IjI1NC4wMDIiIGZpbGw9IiMwMDAiIGZvbnQtc2l6ZT0iMTQiIHRleHRMZW5ndGg9IjEzOC42MSIgZm9udC13ZWlnaHQ9IjcwMCI+W0Zyb20gc3RyaW5nIChsaW5lIDIpIF08L3RleHQ+PHRleHQgeD0iNSIgeT0iMjc0LjgwMSIgZmlsbD0iIzMzRkYwMiIgZm9udC1zaXplPSIxNCIgZm9udC13ZWlnaHQ9IjcwMCI+wqA8L3RleHQ+PHRleHQgeD0iNSIgeT0iMjkxLjYwMSIgZmlsbD0iIzMzRkYwMiIgZm9udC1zaXplPSIxNCIgdGV4dExlbmd0aD0iNzEuMDk1IiBmb250LXdlaWdodD0iNzAwIj5Ac3RhcnR1bWw8L3RleHQ+PHRleHQgeD0iMjkuNjQiIHk9IjMwOC40MDEiIGZpbGw9IiMzM0ZGMDIiIGZvbnQtc2l6ZT0iMTQiIHRleHRMZW5ndGg9IjIwMS43MTEiIGZvbnQtd2VpZ2h0PSI3MDAiIHRleHQtZGVjb3JhdGlvbj0id2F2eSB1bmRlcmxpbmUiPiFpbmNsdWRlIHBhdGgvdG8vZGlhZ3JhbS5wdW1sPC90ZXh0Pjx0ZXh0IHg9IjguMDgiIHk9IjMyNS4yMDEiIGZpbGw9IiNGMDAiIGZvbnQtc2l6ZT0iMTQiIHRleHRMZW5ndGg9IjI0NS44ODUiIGZvbnQtd2VpZ2h0PSI3MDAiPmNhbm5vdCBpbmNsdWRlIHBhdGgvdG8vZGlhZ3JhbS5wdW1sPC90ZXh0Pjw/cGxhbnR1bWwtc3JjIEtyMjAwQ05DbEVJU3FmSEtYT0I0YVdwemFkcHpiQ3A0ekFCNE5CczJxam1TQll2M0ZJQzM4cEN6SW0wMD8+PC9nPjwvc3ZnPg==" class="uml" alt="uml diagram" title="" />
    

Docker Dependencies

The Dockerfile installs the following packages for PlantUML:

  • openjdk17-jre — Java runtime for executing the PlantUML JAR.
  • graphviz — Required for PlantUML's !include and certain diagram types.
  • PlantUML JAR — Downloaded from the official GitHub releases to /opt/plantuml/plantuml.jar.

Disabling PlantUML

To disable PlantUML rendering, comment out the plantuml_markdown block in mkdocs.yml and remove the PlantUML-related packages from the Dockerfile.

PWA (Progressive Web App) Manifest

Overview

The site is installable as a Chrome Progressive Web App (PWA) that launches in a standalone window without browser chrome (address bar, tabs, etc.).

Manifest File (mkdoc/docs/manifest.json)

{
    "name": "Scanframe Wiki",
    "short_name": "Scanframe",
    "start_url": "/",
    "display": "standalone",
    "theme_color": "#1e3c72",
    "background_color": "#1e3c72",
    "icons": [
        {
            "src": "assets/icon-192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "assets/icon-512.png",
            "sizes": "512x512",
            "type": "image/png",
            "purpose": "any maskable"
        }
    ]
}
  • display: standalone — Removes the browser's address bar, tabs, and navigation controls.
  • theme_color — Set to #1e3c72 to match the site's header gradient.
  • Icons — Two sizes generated from the existing 599×599 logo: 192×192 (home screen) and 512×512 (Chrome App installation).

Template Override (mkdoc/overrides/main.html)

A template override injects the manifest link into the HTML <head>:

{% extends "base.html" %}

{% block extrahead %}
{{ super() }}
<link rel="manifest" href="{{ 'manifest.json' | url }}" crossorigin="use-credentials"/>
{% endblock %}

The custom_dir: overrides setting in mkdocs.yml tells Material for MkDocs to use this template.

Custom CSS (mkdoc/docs/stylesheets/extra.css)

Font and Spacing Adjustments

  • Root font size: Reduced from Material's default 125% to 100%.
  • Body text: 0.9rem with 1.5 line height (default is 1.6).
  • Navigation: 0.9rem font size with 1.1 line height.
  • Grid max width: Increased from 61rem to 100rem for wider content areas.
  • Width: Set to 24rem on large screens.
  • Padding: Left padding removed from the sidebar inner container.
  • Title wrapping: Long titles wrap instead of showing ellipsis (...).
  • Chevron icons: Enlarged to 2.5em with vertical centering (align-self: center) for better visibility.

Color Overrides

  • Header: Dark blue gradient from #1e3c72 to #2a5298.
  • Footer meta: Reverse gradient from #2a5298 to #1e3c72.
  • Light mode background: Page background set to #efefef (light gray) instead of white.
  • Light mode code blocks: Code background set to #e0e0e0 to contrast with the gray page background.

Production Domain Styling (Hide Chrome)

When served from www.scanframe.com under the /website path, the following elements are hidden:

  • .md-header — Site header (navigation bar).
  • .md-nav--primary — Navigation sidebar content.
  • .md-footer — Site footer.
  • .md-sidebar--primary — Sidebar container width collapsed to 0 so main content fills the full page.

The hide-chrome class is added to <body> by hide-elements.js when both conditions are met: hostname is www.scanframe.com AND the path starts with /website.

Table Styling

  • Vertical borders: Added 1px right border to all table cells using the theme's --md-default-fg-color--lightest color variable.
  • Clean edges: The right border is removed from the last cell in each row so the table edge stays clean.
  • Font size: Table font size reset from theme default to inherit from parent.

Custom JavaScript (mkdoc/docs/javascripts/hide-elements.js)

The script detects the current domain and path, then adds the hide-chrome class to <body> when both conditions are met:

  • location.hostname is www.scanframe.com.
  • location.pathname starts with /website.

The script uses Material's document$ subscription pattern (when available) to run on every SPA navigation, ensuring the class persists across page changes. On non-matching hosts or paths, the script does nothing.

CPU Optimization

Problem

The default MkDocs livereload module polls for file changes every 0.5 seconds, causing approximately 5.5% idle CPU usage even when no files are being edited.

Solution

The polling interval is patched in the Dockerfile from 0.5 seconds to 600 seconds (10 minutes):

RUN sed -i 's/polling_interval: float = 0\.5/polling_interval: float = 600/' \
    /usr/local/lib/python3.11/site-packages/mkdocs/livereload/__init__.py

This effectively stops the constant file system polling while keeping the server running. The idle CPU usage drops from 5.5% to approximately 0.05%.

Trade-off

With polling disabled, automatic live reload is no longer functional. The watch section in mkdocs.yml is commented out, and manual rebuilds via build.sh restart or event-driven watching via build.sh auto-restart are used instead.

Alternative: Event-Driven Watching

The auto-restart command in build.sh provides automatic rebuilds with near-zero CPU usage by using Linux's inotify kernel event system instead of polling. This gives the convenience of automatic rebuilds without the CPU overhead.

Icon Assets

File Size Purpose
assets/scanframe.png 100×100 Site logo in the header.
assets/scanframe-large.png 599×599 Source image for PWA icons.
assets/scanframe.ico — Browser favicon.
assets/icon-192.png 192×192 PWA home screen icon (generated from large logo).
assets/icon-512.png 512×512 PWA installation icon (generated from large logo).

Enabling and Disabling Features

To Enable File Watching

  1. Uncomment the watch section in mkdoc/mkdocs.yml.
  2. Rebuild the Docker image: ./build.sh build.

To Use Event-Driven Auto-Restart

  1. Install inotify-tools: apt-get install inotify-tools.
  2. Run ./build.sh auto-restart in the foreground.
  3. Press Ctrl+C to stop watching.

To Disable PlantUML

  1. Comment out the plantuml_markdown block in mkdoc/mkdocs.yml.
  2. Remove the Java, Graphviz, and PlantUML JAR installation steps from mkdoc/Dockerfile.
  3. Rebuild the Docker image.

To Change Colors

Edit the primary and accent values under the palette section in mkdocs.yml. See the Material for MkDocs color documentation for available colors.

To Add a Navigation Section

Edit mkdoc/docs/_navigate_.yml and add a new entry with an emoji prefix and the content directory path.

To Rename a Subdirectory in Navigation

Create a _navigate_.yml file in the target subdirectory with a title: field, for example:

title: ⚙️ Configuration

This overrides the auto-discovered directory name in the navigation sidebar.