Files
Anthias/docs/developer-documentation.md
Viktor Petersson 2d7b92c006 Hugo website: docs migration, API reference, FAQ, and SEO (#2807)
* Move website to Hugo

* Rewrite in progress

* Add Hugo-native API reference page and fix CSS build path

Two related changes for the Hugo site:

1. CSS build target: package.json's css:build/css:watch wrote to
   assets/styles/style.css, but baseof.html uses a plain <link href>
   that Hugo serves from static/. The merge left a stale 14K static
   copy alongside the freshly-built 23K asset copy, so pages rendered
   with most utility classes undefined. Build target is now
   static/assets/styles/style.css, matching the convention used by
   every other website asset.

2. Hugo-native API docs at /api/. The OpenAPI spec is loaded from
   data/openapi.yaml (generated via `manage.py spectacular`) and
   rendered in layouts/_default/api.html and a recursive schema
   partial. Endpoints are grouped by tag with anchor jumps, color-
   coded method badges, params/request/response tables, and inline
   $ref resolution. Renders all 18 v2 endpoints across 9 tags with
   the existing Tailwind theme. No third-party JS bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Move documentation under Hugo and redirect old GitHub paths

Migrates docs/ markdown into website/content/docs/ rendered with a new
docs/ layout (list + single) and Tailwind prose styling. Images and
the d2 diagram move to website/static/docs/. Internal links rewritten
from /docs/foo.md to /docs/foo/, and GitHub-style alerts pre-converted
to bold-labeled blockquotes since the goldmark alert extension is not
enabled on this Hugo version.

The original docs/*.md files are kept as redirect stubs that point at
https://anthias.screenly.io/docs/... so external links into the GitHub
docs tree still resolve to a useful page. Root README.md links updated
to point at the website URLs.

Hugo nav now exposes Docs alongside Features / Get Started / API / FAQ.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix factual inaccuracies in migrated docs against the codebase

Reviewed all docs against the current source. Concrete fixes:

* _index.md: container names use the post-rebrand `anthias-` compose
  project prefix (e.g. `anthias-anthias-server-1`, `anthias-redis-1`)
  rather than the legacy `screenly-` form. Replaced `docker-compose
  logs` with `docker compose logs` and added the optional `anthias-
  caddy` sidecar to the container table.
* developer-documentation.md: fixed leading-letter typo ("unning"),
  and replaced the old Django test-runner invocation with the pytest
  commands used by the suite today (`pytest -n auto -m "not
  integration"` and `pytest -m integration`).
* balena-fleet-deployment.md: corrected the supported board list
  ($BOARD_TYPE) to match `bin/deploy_to_balena.sh --help`
  (`pi2`, `pi3`, `pi4-64`, `pi5` — no `pi1` or plain `pi4`). Updated
  registry reference from Docker Hub to GHCR.
* migrating-assets-to-screenly.md: `cd ~/screenly` → `cd ~/anthias`
  (post-rebrand install path).
* raspberry-pi5-ssd-install-instructions.md: fixed "Opitions" and
  "uinsg" typos in the boot-order steps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Polish docs styling: callouts, syntax highlighting, hierarchy

Reworks docs prose styling so the migrated pages don't read like
default-Hugo-render-output:

* Headings: in-body H1/H2 collapse to a section divider style with a
  top border so they don't compete with the dark page hero. H4-H6
  become small uppercase eyebrows. Markdown sources mix #/##/####
  inconsistently — the visual scale now compresses gracefully.
* Alerts: a render-blockquote hook detects the bold-label preamble
  produced by our preprocessor (`> **Note**` etc.) and emits a typed
  `<blockquote class="docs-alert docs-alert-note">` so each kind gets
  its own colored border + label (note/tip/important/warning/caution).
* Syntax highlighting: enable Hugo Chroma with the github style,
  noClasses=false. Generated chroma.css ships as a static asset and is
  loaded alongside style.css. `pre`/`<code>` get a light surface that
  the chroma token colors sit on top of.
* Inline code, lists, links, tables, and images all get a small
  rebalance — bullet color, link underline weight, image shadow,
  table border-radius — to match the brand-purple theme.
* Footer: the Resources / Docs link pointed at the legacy
  github.com/.../docs/README.md path; now points at /docs/. Added an
  API Reference link alongside.
* Stripped a stray `<br>` in the Pi5 SSD doc that was creating a
  random gap between a blockquote and its illustrative screenshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Make x86/PC docs consistent and more user-friendly

The migrated docs used four different forms — "x86", "x86 device",
"PC (x86) devices", and "PC (x86 Devices)" — depending on the page.
Standardize on **PC (x86)** as the user-facing label (PC is what
people search for; x86 stays as the architecture qualifier).

Also rewrites x86-installation.md from a flat bullet dump into a
clearer five-step walkthrough — what you need, download, flash,
install Debian, prep the system, run the installer — and crosslinks
the right anchor in installation-options.md so PC users can hand off
to the scripted install without scrolling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Expand FAQ with forum-driven questions and refactor to data file

The FAQ had six entries that didn't reflect what people actually ask
on forums.screenly.io. Reviewed the all-time top topics and added the
ones that show up over and over: portrait rotation, YouTube playback,
Wi-Fi setup, static IP assignment, audio output, resolution / 4K,
black-screen troubleshooting, transitions, asset storage / backup,
SSH, HTTPS pointer, commercial-use clarity, getting logs, and a link
to the API reference.

Refactored the layout so it reads from data/faq.yaml grouped by
section (About, Installation & updates, Display & playback,
Operations) and renders each answer through markdownify. This makes
adding new entries a one-paragraph YAML edit instead of duplicating
~15 lines of accordion markup. Answers reuse the .docs-prose styling
so code, links, lists, and inline pre snippets all match the docs
pages.

Also tightened the "Accessing the REST API" section in /docs/ to
point at the new /api/ page first, with the live ReDoc URL on the
device as a secondary callout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Correct rotation FAQ — Anthias renders via linuxfb + DRM, no Wayland

Verified in code: docker/Dockerfile.viewer.j2 sets
QT_QPA_PLATFORM=linuxfb, webview/build_qt{5,6}.sh both pass
-skip wayland to the Qt build, and viewer/media_player.py invokes
mpv with --vo=drm. There is no Wayland compositor in the runtime
stack on any board.

Replaced the previous "Pi 5 with Wayland uses a different stack"
hand-wave with the actual fallback: if /boot/firmware/config.txt's
display_rotate=N doesn't stick on a Pi 5 / KMS pipeline, append
video=HDMI-A-1:...,rotate=N to /boot/firmware/cmdline.txt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Tighten three FAQ answers after a code-driven validation pass

* SSH: previous answer claimed SSH was on by default for both the
  Anthias disk image and the scripted install. Anthias's installer
  doesn't touch sshd at all, so the answer now distinguishes between
  the prebuilt images (SSH on) and a self-flashed Raspberry Pi OS Lite
  (SSH must be pre-enabled).
* Audio output: static/src/components/settings/audio-output.tsx hides
  the 3.5mm option on Pi 5 because the hardware lacks the jack. Call
  that out so Pi 5 users don't go looking for a missing dropdown item.
* Black screen: replaced the `xset dpms force on` suggestion. Anthias
  has no X server on any board (Qt runs on linuxfb, mpv on --vo=drm),
  so xset can't toggle DPMS. Pointed users at re-seating HDMI or
  checking the TV's input as a more grounded recovery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Correct features-page claim — Anthias detects display state, can't toggle it

The "Display power control" card promised programmatic on/off
toggling of the connected screen for energy savings. That isn't a
real feature. lib/diagnostics.py only calls libcec's tv.is_on() to
*query* the TV's power state — there's no power-on / standby command
path anywhere in the codebase. The result surfaces read-only as
display_power on the System Info page (static/src/components/
system-info.tsx).

Replaced the card with what's actually shipping: HDMI-CEC display
state *detection*, visible on the System Info page.

Verified the rest of the page against code while I was in there.
Accurate as written: image/video/webpage assets (Qt webview + mpv),
scheduling (start_date/end_date/duration on the asset model), drag-
drop playlists (@dnd-kit/sortable), shuffle (settings.shufflePlaylist),
1080p output (mpv pinned to 1920x1080@60 on pi4-64/pi5 in
viewer/media_player.py), real-time WebSocket sync (Django Channels +
Redis pub/sub), REST API (drf-spectacular), four-container compose
topology, backup/restore, optional basic auth (lib/auth.py BasicAuth),
System Info page fields (loadavg/free_space/uptime/anthias_version),
and the supported hardware list (matches ansible/site.yml's
device_type assertion).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Punchier homepage tagline: "Free digital signage for everyone."

Replaces "Open source digital signage for any screen" with a shorter,
benefit-led headline. The new line breaks naturally across two lines
on desktop (free digital signage / for everyone.) and stays single-
line on mobile to avoid an awkward orphan.

Subtitle is unchanged — it still does the explanatory work (Pi or
PC, schedule images/videos/webpages, no subscriptions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SEO sweep: per-page meta, FAQPage / TechArticle JSON-LD, robots.txt

Two functional gaps in the existing setup:

* og:description and twitter:description were hardcoded to a single
  marketing line on every page, while the per-page <meta name=
  description> already pulled from front matter. So the Slack/Twitter/
  Discord card preview always read the same blurb regardless of which
  page you shared. Now both the OG and Twitter description reflect the
  page's own .Params.description.

* Page titles drifted: most pages embedded "Anthias" in the title
  string, but the docs pages were just "Documentation" /
  "Installation Options" / etc. — fine for the H1, weak for SERPs.
  Title now appends " | Anthias" only when the page title doesn't
  already contain the brand, so existing branded titles stay clean
  and docs pages get a brand suffix automatically.

Other tightening:

* Added FAQPage structured data on /faq/ generated from data/faq.yaml
  so Google can surface FAQ rich results.
* Added TechArticle structured data on individual /docs/ pages.
* og:type now flips to "article" on docs pages.
* og:image:alt + twitter:image:alt populated.
* theme-color set to the brand purple for mobile browser chrome.
* JSON-LD home schema URL now uses site.BaseURL instead of a
  hardcoded production URL — important for staging / dev parity.
* <html lang> reads site.LanguageCode instead of a fixed "en".
* Added a real robots.txt that points crawlers at /sitemap.xml
  (Hugo already generates the sitemap, but a robots.txt makes the
  pointer explicit and unblocks tooling that looks for it).

Replaced placeholder image alt text in the docs ("balena-ss-01",
"imager-01", "rpi-eeprom-update", etc.) with descriptive captions —
better for screen readers and image-search SEO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Move site assets into Hugo's expected layout and rename /docs URLs

Two related cleanups.

ASSETS — site assets were split across website/static/assets/ (the
shadowed copy hugo.toml's [[module.mounts]] directed traffic to) and
website/assets/ (an unused duplicate). Hugo's own build report showed
"Processed images: 0" because nothing actually flowed through Pipes.

  * Removed the [[module.mounts]] override so Hugo uses default
    layout: assets/ for Pipes-processable resources, static/ for
    served-as-is files.
  * Used `git mv` to record the docs/ image and stylesheet renames as
    history-preserving moves rather than delete+add diffs.
  * Removed the duplicate website/static/assets/images/ directory —
    files already lived in website/assets/images/.
  * Bun's css:build/css:watch now write to assets/styles/style.css so
    Tailwind output flows through Hugo Pipes.
  * baseof.html loads style.css and chroma.css via resources.Get +
    fingerprint, with SRI integrity attributes. Each deploy produces
    a fresh content-hashed URL (/styles/style.<hash>.css), so the
    browser cache invalidates correctly without manual cache-busting.
  * Logos, social icons, hero raster (overview*.png), favicon, and
    plus/minus accordion icons all flow through resources.Get for
    consistent asset handling.
  * Added layouts/_default/_markup/render-image.html so markdown
    image references in /docs are looked up via resources.Get and
    emitted with loading="lazy" decoding="async".

URL RENAMES — the docs URLs were verbatim copies of the original
GitHub filenames, which made for noisy URLs like
/docs/raspberry-pi5-ssd-install-instructions/. Slugged each page and
left aliases for the old paths so Hugo emits a meta-refresh redirect:

  /docs/installation-options/                       → /docs/install/
  /docs/balena-fleet-deployment/                    → /docs/balena/
  /docs/x86-installation/                           → /docs/pc/
  /docs/raspberry-pi5-ssd-install-instructions/     → /docs/pi5-ssd/
  /docs/migrating-assets-to-screenly/               → /docs/migrate-to-screenly/
  /docs/qa-checklist/                               → /docs/qa/
  /docs/developer-documentation/                    → /docs/development/

Cross-doc links inside /docs and the README + repo-root docs/ stub
files all point at the new URLs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix lint + mypy on raspberry_pi_imager test (carried over from rebase)

The test_build_pi_imager_json.py file landed in `88d3881b Move website
to Hugo` with two pre-existing CI failures:

* ruff format --check: a few helper definitions had a stale line
  break the formatter wanted to collapse.
* mypy: `make_image_metadata(board: str) -> dict` is missing the
  generic type parameters that the project's mypy config flags as
  type-arg. Annotated as `dict[str, Any]`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Self-host Plus Jakarta Sans via @fontsource (drop Google Fonts CDN)

Removes the third-party Google Fonts <link>. SonarCloud's Web:S5725
hotspot was flagging the link as a resource-integrity (SRI) risk —
SRI is impossible against Google Fonts because the served stylesheet
rotates per User-Agent and the woff2 URLs change with the font CSS.

Self-hosting the same font from npm via @fontsource removes the
cross-origin resource entirely.

How it's wired:

* `bun add -D @fontsource/plus-jakarta-sans` for the font binaries.
* `scripts/install-fonts.ts` is a small bun script that, given the
  installed package, copies woff2 files for latin + latin-ext at
  weights 400/500/600/700/800 to `static/fonts/` (so Hugo serves
  them at `/fonts/...`) and emits a combined
  `assets/fonts/plus-jakarta-sans.css` with the urls rewritten to
  absolute /fonts/... paths and the woff fallback stripped.
* `package.json` adds `fonts:install`, and chains it through
  `css:build` / `css:watch` so Tailwind always sees the generated
  CSS up to date.
* `main.css` @imports the generated CSS — Tailwind/Lightning CSS
  inlines the @font-face rules into the final fingerprinted
  style.<hash>.css.
* `.gitignore` excludes `assets/fonts/` and `static/fonts/` since
  both are deterministically regenerated from node_modules.
* `baseof.html` no longer pulls from fonts.googleapis.com.

Total payload: 10 woff2 files (~136KB), but each is loaded
on-demand by unicode-range — typical English-only visitors fetch
~50KB of fonts, served from same-origin.

The second Web:S5725 hotspot (gtag.js from googletagmanager.com)
is unchanged in this commit — Google's tag manager script is
updated server-side without a stable hash, so SRI cannot apply.
That one needs a product call (keep with dismissal, drop GA, or
move to a privacy-first SRI-friendly alternative).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address SonarCloud code-smell findings on the website

Cleared the unrelated SonarCloud findings raised on this PR:

* `install-fonts.ts`: `fs` and `path` imports use the `node:` prefix
  (typescript:S7772). The new prefixed form is the bun-recommended
  one, no behavior change.
* `_markup/render-image.html`: rewrote the comment that referenced
  `<img>` literally — Web:ImgWithoutAltCheck was treating the word
  inside the Hugo comment block as an actual element with no alt.
* `_default/faq.html`: replaced the accordion's `<div role="region">`
  with a real `<section>` element (Web:S6819). The aria-labelledby
  binding stays, so the accessible name resolution is identical and
  the semantics are now native rather than ARIA-emulated.
* `assets/styles/chroma.css`: stripped the two stray-semicolon lines
  left over from the sed pass that emptied the github-style backdrop
  (css:S1116). The remaining `.chroma { -webkit-text-size-adjust:
  none }` rule is what's actually load-bearing.
* `_default/baseof.html`:
  - accordion JS now reads `this.dataset.accordion` instead of
    `this.getAttribute('data-accordion')` (javascript:S7761).
  - GA bootstrap uses `globalThis.dataLayer` instead of
    `window.dataLayer` (javascript:S7764). Same semantics in any
    browser context, no globalThis polyfill needed for our targets.
* `layouts/index.html`: dropped the deprecated `scrolling="0"`
  attribute from the GitHub stars iframe (Web:S1827); replaced
  with the equivalent `overflow-hidden` Tailwind class.

The Web:S5725 SRI hotspot on the gtag.js script (line 162 of
baseof.html) is the only remaining finding. Google Tag Manager is
versioned server-side without a stable hash, so SRI fundamentally
can't apply — that one is being kept and dismissed in the
SonarCloud UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address Copilot review + trigger marketing deploy on release publish

Copilot review:

* api.html: request-body renderer only looked at application/json,
  so endpoints whose only content type is multipart/form-data (file
  uploads) or application/x-www-form-urlencoded would render an
  empty Request body section. Pick application/json first if present,
  otherwise fall back to the first listed content type, and label
  the rendered schema with its actual content type.
* build_pi_imager_json.py: every requests.get() now sets a 30s
  timeout and calls raise_for_status() so a slow/rate-limited GitHub
  API doesn't hang the deploy job and a 4xx/5xx fails fast with a
  clear message rather than a confusing KeyError on response.json().
* docs/raspberry-pi5-ssd-install-instructions.md: "Other HAT's" →
  "Other HATs".
* docs/qa-checklist.md: dropped the spurious "a" in "Change a the
  start and end dates".
* deploy-website.yaml: jq's has() takes one key, so the validation
  step `has("name", "description", ...)` was actually a syntax error
  on every run — rewrote as `all($k; $entry | has($k))` over the
  required-key list.
* layouts/_default/get-started.html: the "Documentation" CTA pointed
  at the old GitHub markdown file; now links to /docs/ to match the
  navbar / footer.
* website/README.md: rewrote the project-structure tree to match
  what's actually in the repo (data/, scripts/, layouts/docs/,
  Goldmark _markup/ hooks etc.) and documented the bun pipeline —
  `hugo server` alone leaves /fonts/* as 404s because the woff2
  files are gitignored and materialized by `bun run fonts:install`.

Marketing deploy on release publish:

`build-balena-disk-image.yaml` cuts the GitHub release with the
*.img.zst artefacts as its final step; until now the marketing site
only re-deployed on master push or manual dispatch, so rpi-imager.
json on the live site lagged the freshest disk images by however
long it took someone to push an unrelated website change. Hooking
deploy-website.yaml to `on: release: types: [published]` makes the
site rebuild as soon as the release exists, which is exactly when
the GitHub API starts surfacing the new assets the JSON generator
queries. `prerelease=true` releases are included because that's what
build-balena-disk-image.yaml currently flags every release as.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address second round of Copilot review

* installation-options.md: balenaEthcher → balenaEtcher.
* balena-fleet-deployment.md: includa → include.
* developer-documentation.md: spash screen → splash screen.
* qa-checklist.md: enabling **Show splash screen** is supposed to
  *display* the splash, not hide it — flipped "is not being
  displayed" → "is being displayed". Also clickin → clicking.
* raspberry-pi5-ssd-install-instructions.md: `sudo apt update -y`
  isn't valid (apt's -y is only for install / upgrade), so the
  copy-paste step would error. Dropped the `-y` from update; the
  full-upgrade line keeps it because that's where it actually does
  something.
* deploy-website.yaml: the jq required-keys check was missing
  `icon` and `website`, which build_pi_imager_json.py's
  REQUIRED_FIELDS already enforces in the Python tests. Added them
  so the runtime validation matches the generator's contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address third round of Copilot review

* website/.gitignore: `_.log` was a typo from the original Hugo
  bootstrap — it doesn't match anything. Replaced with the intended
  `*.log` so log files are actually ignored.
* website/package.json: rewrote the `dev` script to capture both
  child PIDs and trap EXIT/INT/TERM so Ctrl-C (or hugo crashing)
  takes the Tailwind watcher down with it. Mirrors the pattern in
  the repo-root package.json's `dev`.
* docs/raspberry-pi5-ssd-install-instructions.md: "Early Pi 5's"
  → "Early Pi 5s" (no apostrophe on plurals).
* docs/qa-checklist.md: "make sure that the screen in standby mode"
  → "make sure that the screen is in standby mode" (missing verb).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Rewrite raspberry_pi_imager tests in pytest style

The file was unittest.TestCase classes — pytest discovers and runs
those, but the boilerplate doesn't earn its keep. Each test method
re-declared `@patch('...requests.get')` and rebuilt the same
MagicMock setup, and the per-board cases lived as 5+3+2+2 separate
methods that should have been one parametrize each.

Reworked as flat module-level functions backed by three fixtures:

* `mock_requests_get` — patches the module's `requests.get` and yields
  the mock so each test sets `return_value` / `side_effect` directly.
* `mock_release_assets` — preconfigured to return the canned release
  asset list, used by the `get_asset_list` cases.
* `mock_full_build` — wires up the three call shapes
  `build_imager_json()` makes (latest, asset list, per-asset json).

Per-board cases collapse into `@pytest.mark.parametrize`:
get_board_from_url's positive cases, the non-image-returns-None
cases, the maintenance-mode boards, and the modern boards.

Coverage is the same — 21 collected cases (pytest fans the parametrize
out from 12 test methods to 21 ids), all passing in 0.12s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix PR checks and Copilot review items

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 09:16:25 +01:00

6 lines
227 B
Markdown

# Developer Documentation
This page has moved to **[https://anthias.screenly.io/docs/development/](https://anthias.screenly.io/docs/development/)**.
The Anthias documentation now lives at <https://anthias.screenly.io/docs/>.