Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ PROJECT_NAME="airstack"
# If you've run ./airstack.sh setup, then this will auto-generate from the git commit hash every time a change is made
# to a Dockerfile or docker-compose.yaml file. Otherwise this can also be set explicitly to make a release version.
# auto-generated from git commit hash
VERSION="0.20.6"
VERSION="0.20.7"
# Image-tag discriminator ONLY (appears in the image tag suffix, e.g. ..._robot-x86-64_dev).
# No Dockerfile consumes it: "prebuilt" does NOT bake the built ros_ws into the image today —
# a real prebuilt (workspace-baked) stage is future work. Keep "dev" (mounted code, built live).
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/deploy_docs_from_develop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ jobs:
git config --global user.name "Docs Deploy"
git config --global user.email "docs.deploy@example.co.uk"
- name: Build Docs Website
env:
# "Edit this page" links on the develop docs edit the develop
# branch (mkdocs.yml defaults edit_uri to edit/main/).
DOCS_EDIT_URI: edit/develop/
run: |
VERSION=$(grep -m1 '^VERSION=' .env | cut -d= -f2- | tr -d '"')
mike deploy --push --title "${VERSION} (unstable)" develop
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/deploy_docs_from_main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- "stacks/**"
- "tools/gen_docs_catalog.py"
- ".github/workflows/deploy_docs_from_main.yaml"
- ".github/workflows/templates/root-redirect.html"
branches:
- main
# Serialize gh-pages pushes: concurrent docs deploys race on the branch
Expand Down Expand Up @@ -74,8 +75,11 @@ jobs:
mike deploy --push --update-aliases --title "${VERSION}" "${SLUG}" main
# Root URL redirects to the PINNED slug (not the moving alias): URLs
# people land on and copy stay valid across future releases. /main/
# remains as a moving alias for deep links.
mike set-default "${SLUG}" --push
# remains as a moving alias for deep links. The custom template adds
# a real <title> and Open Graph tags so a shared docs.theairlab.org
# link previews properly instead of as "Redirecting" (mike renders
# it with {{href}} = the slug).
mike set-default "${SLUG}" --push -T .github/workflows/templates/root-redirect.html
# mike re-sorts versions.json on every deploy, so pin develop — the
# higher, unreleased version — back to the top of the version selector
# after each deploy.
Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/templates/root-redirect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<!-- Custom template for `mike set-default -T` (deploy_docs_from_main.yaml).
Same instant redirect as mike's default, plus real <title>/description
and Open Graph / Twitter Card tags: link-preview scrapers (Slack,
WhatsApp, iMessage, ...) fetch this page's raw HTML and neither run
JavaScript nor follow meta-refresh, so without these tags a shared
https://docs.theairlab.org/ link previews as just "Redirecting".
mike substitutes {{href}} with the default version's slug (e.g. "0.20/").
The og:image points at the moving /main/ alias so the URL stays valid
across releases without editing this file. -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>AirStack Documentation</title>
<meta name="description" content="AirStack is an open-source ROS 2 autonomy stack for aerial robots from the AirLab at Carnegie Mellon University — simulation, ground control, and layered onboard autonomy that launch as one system.">
<meta property="og:type" content="website">
<meta property="og:title" content="AirStack Documentation">
<meta property="og:description" content="An open-source ROS 2 autonomy stack for aerial robots — simulation, ground control, and layered onboard autonomy that launch as one system. By the AirLab at Carnegie Mellon University.">
<meta property="og:url" content="https://docs.theairlab.org/">
<meta property="og:image" content="https://docs.theairlab.org/main/docs/assets/media/splash-poster.jpg">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="1600">
<meta property="og:image:height" content="900">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="AirStack Documentation">
<meta name="twitter:description" content="An open-source ROS 2 autonomy stack for aerial robots — simulation, ground control, and layered onboard autonomy that launch as one system.">
<meta name="twitter:image" content="https://docs.theairlab.org/main/docs/assets/media/splash-poster.jpg">
<noscript>
<meta http-equiv="refresh" content="1; url={{href}}" />
</noscript>
<script>
window.location.replace(
"{{href}}" + window.location.search + window.location.hash
);
</script>
</head>
<body>
Redirecting to <a href="{{href}}">{{href}}</a>...
</body>
</html>
73 changes: 73 additions & 0 deletions docs/hooks/social_meta_redirect_stub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Add link-preview metadata to the version-root redirect stub.

With ``docs_dir: .`` the site's version root (e.g. ``/0.20/``) is a bare
redirect page generated by mkdocs-redirects (``index.md`` -> ``docs/index.md``)
titled "Redirecting...". Link-preview scrapers (Slack, WhatsApp, iMessage, ...)
read that page's raw HTML and neither run JavaScript nor follow meta-refresh,
so a shared version-root URL previews as just "Redirecting...".

This hook rewrites the generated ``<site_dir>/index.html`` after the build:
a real title plus the same Open Graph / Twitter Card block the theme override
(docs/overrides/main.html) puts on regular pages. MkDocs runs hooks after
plugins for each event, so the redirect stub already exists on_post_build.
"""

import logging
import os
import re

log = logging.getLogger("mkdocs.hooks.social_meta_redirect_stub")

TITLE = "AirStack Documentation"
DESCRIPTION = (
"An open-source ROS 2 autonomy stack for aerial robots — simulation, "
"ground control, and layered onboard autonomy that launch as one system. "
"By the AirLab at Carnegie Mellon University."
)
# The moving /main/ alias keeps this absolute URL valid across releases
# (mike copies real asset files into alias directories).
IMAGE = "https://docs.theairlab.org/main/docs/assets/media/splash-poster.jpg"

META_BLOCK = f"""\
<meta name="description" content="{DESCRIPTION}">
<meta property="og:type" content="website">
<meta property="og:title" content="{TITLE}">
<meta property="og:description" content="{DESCRIPTION}">
<meta property="og:image" content="{IMAGE}">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="1600">
<meta property="og:image:height" content="900">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{TITLE}">
<meta name="twitter:description" content="{DESCRIPTION}">
<meta name="twitter:image" content="{IMAGE}">
"""


def on_post_build(config, **kwargs):
index_path = os.path.join(config["site_dir"], "index.html")
if not os.path.isfile(index_path):
log.warning("site index.html not found; skipping social meta injection")
return
with open(index_path, encoding="utf-8") as f:
html = f.read()
if "Redirecting" not in html:
# Not the redirect stub this hook expects (e.g. the root became a
# real page) — leave it alone.
log.info("site index.html is not a redirect stub; skipping")
return
html, n_title = re.subn(
r"<title>[^<]*</title>", f"<title>{TITLE}</title>", html, count=1
)
html, n_head = re.subn(
r"</head>", META_BLOCK + "</head>", html, count=1, flags=re.IGNORECASE
)
if not (n_title and n_head):
log.warning(
"could not inject social meta into redirect stub "
"(title replaced: %d, head found: %d)", n_title, n_head
)
return
with open(index_path, "w", encoding="utf-8") as f:
f.write(html)
log.info("injected social meta into version-root redirect stub")
37 changes: 37 additions & 0 deletions docs/overrides/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% extends "base.html" %}

{#-
Open Graph / Twitter Card tags for link previews (Slack, WhatsApp,
iMessage, ...). Material only emits these via its `social` plugin, which
we don't run — this block provides them statically on every page.
home.html extends this template, so the whole site inherits the tags.
og:image uses the moving /main/ alias (mike copies real asset files into
alias dirs) so the absolute URL stays valid across releases.
og:url is deliberately omitted: site_url doesn't include the mike version
slug, so a computed canonical would point at a non-existent path.
-#}
{% block extrahead %}
{% if page and page.meta and page.meta.title %}
{% set og_title = page.meta.title ~ " - " ~ config.site_name %}
{% elif page and page.title and not page.is_homepage %}
{% set og_title = page.title ~ " - " ~ config.site_name %}
{% else %}
{% set og_title = config.site_name %}
{% endif %}
{% if page and page.meta and page.meta.description %}
{% set og_description = page.meta.description %}
{% else %}
{% set og_description = config.site_description %}
{% endif %}
<meta property="og:type" content="website">
<meta property="og:title" content="{{ og_title }}">
<meta property="og:description" content="{{ og_description }}">
<meta property="og:image" content="https://docs.theairlab.org/main/docs/assets/media/splash-poster.jpg">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="1600">
<meta property="og:image:height" content="900">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ og_title }}">
<meta name="twitter:description" content="{{ og_description }}">
<meta name="twitter:image" content="https://docs.theairlab.org/main/docs/assets/media/splash-poster.jpg">
{% endblock %}
26 changes: 26 additions & 0 deletions docs/release_notes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,32 @@ its own notes. -->

- Nothing yet.

## 0.20.7 — 2026-08-30

- **Fixed 404s on every README-sourced docs page.** The `exclude_docs`
pattern intended to hide the repo-root `README.md` used gitignore
semantics, so it silently excluded *every* `README.md` in the tree —
404ing the System Test Suite (`/tests/`), OSMO Lab Admin Guide
(`/osmo/`), and all autonomy package reference pages. The pattern is
now anchored to the repo root (`/README.md`).
- **Shared docs links now show real previews.** Sharing
`docs.theairlab.org` in Slack/WhatsApp/iMessage previewed as
"Redirecting" — preview scrapers don't run JavaScript, and the site
root, the version root (`/0.20/`), and every content page lacked
Open Graph metadata. The root redirect now deploys from a template
with proper title/description/image tags (`mike set-default -T`), a
build hook enriches the version-root redirect stub, and a theme
override (`docs/overrides/main.html`) plus `site_description` add
per-page Open Graph / Twitter Card tags across the site. Note:
previously shared links may keep the stale preview until the apps'
link caches expire.
- **"Edit this page" button on every docs page.** Each page now shows a
pencil icon (Material `content.action.edit` + `edit_uri`) linking to
GitHub's editor for that page's source file; contributors without
write access get GitHub's fork-and-propose flow. Pages built from
`main` link to `edit/main/`, the develop docs to `edit/develop/`
(via `DOCS_EDIT_URI` in the develop deploy workflow).

## 0.20.6 — 2026-08-29

- **Release notes now carry a dated section per hotfix.** Added the
Expand Down
20 changes: 18 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
copyright: Copyright &copy; 2024 - 2030 AirLab CMU
docs_dir: .
site_name: AirStack
site_description: >-
AirStack is an open-source ROS 2 autonomy stack for aerial robots from the
AirLab at Carnegie Mellon University — simulation, ground control, and
layered onboard autonomy that launch as one system.
site_dir: ../site
site_url: "https://docs.theairlab.org/docs/" # Trailing slash is recommended
exclude_docs: |
# The repo README is GitHub-facing; the site's home page is docs/index.md,
# and same-dir would otherwise collide on the root URL.
README.md
# and same-dir would otherwise collide on the root URL. Patterns are
# gitignore-style: the leading slash anchors this to the repo root only —
# a bare README.md would exclude every package/tests/osmo README the nav
# serves as pages.
/README.md
**/ros_ws/build
**/docker/Foxglove
**/ros_ws/install
Expand Down Expand Up @@ -40,6 +47,9 @@ extra_css:
# build time, so each mike-deployed docs version shows only its own notes.
hooks:
- docs/hooks/release_notes_current_version.py
# Give the version-root redirect stub (/X.Y/ -> docs/) a real title and
# Open Graph tags so shared links preview properly (Slack, WhatsApp, ...).
- docs/hooks/social_meta_redirect_stub.py
markdown_extensions:
- admonition
- attr_list
Expand Down Expand Up @@ -243,6 +253,11 @@ plugins:
'docs/tutorials/index.md': 'docs/getting_started/tutorials_reference.md'
repo_name: castacks/AirStack
repo_url: https://github.com/castacks/AirStack
# Per-page "Edit this page" pencil (theme feature content.action.edit).
# docs_dir is the repo root, so page paths are already repo-relative.
# GitHub shows contributors without write access its fork-and-edit flow.
# The develop docs deploy overrides the branch via DOCS_EDIT_URI.
edit_uri: !ENV [DOCS_EDIT_URI, "edit/main/"]
theme:
favicon: docs/assets/StackedWhite.png
custom_dir: docs/overrides
Expand All @@ -266,6 +281,7 @@ theme:
- content.code.copy
- content.code.annotate
- content.tooltips
- content.action.edit
font:
text: Inter
code: JetBrains Mono
Expand Down
Loading