docs: migrate to Starlight + merge authoring into sourcebans-pp (#1333)#1339
Merged
Conversation
Workstream 1 of the Hugo→Starlight migration. New docs/ directory holds the Astro + Starlight project that publishes to sbpp.github.io once the cutover lands. The sibling repo becomes a thin deploy shell; source of truth lives here so user-facing changes ship with their docs in the same PR. Pieces in this commit: * docs/package.json — astro 5 + @astrojs/starlight + astro-mermaid + @playwright/test (drives docs/scripts/capture.mjs in a later commit) + sharp (Astro asset pipeline). Pagefind ships inside Starlight; no separate dep. * docs/astro.config.mjs — site=https://sbpp.github.io/, base=/, full sidebar tree (Getting Started, Setup, Updating, Troubleshooting, Integrations, Customization, FAQ, Legacy), GitHub + Discord social links, edit-link back to this repo. ThemeProvider override (next bullet) is wired here so first paint matches the panel. * docs/src/components/ThemeProvider.astro — overrides Starlight's stock dark-leaning fallback with a default of 'light'. Mirrors the panel's first-paint behavior; user toggle still wins on subsequent visits via localStorage['starlight-theme']. * docs/src/assets/logo.svg + docs/public/favicon.svg — same brand mark as the panel (web/themes/default/images/favicon.svg). * docs/src/assets/auto/{install,panel}/.gitkeep — destination for the auto-capture pipeline (Workstream 7); committed empty so layout is in place before the first capture run. * docs/tsconfig.json — extends astro/tsconfigs/strict. * .gitignore — append docs/{node_modules,dist,.astro,pnpm-lock}. Auto-captured screenshots under docs/src/assets/auto/ stay TRACKED so the screenshot diff lands with the UI change. * .github/dependabot.yml — add npm ecosystem rooted at /docs/. Theme tokens land in the next commit (Workstream 2).
Workstream 2. New docs/src/styles/sbpp.css carries the panel-parity
overrides wired in via the customCss array in astro.config.mjs.
The mapping is token-for-token from web/themes/default/css/theme.css
(panel :root + html.dark blocks → Starlight's --sl-color-* /
--sl-color-bg-* / --sl-color-asides-* hooks):
* Brand orange (--brand-50 … --brand-950) → --sl-color-accent triplet
+ accent-text. Light mode uses --brand-600 as primary / --brand-700
as hover/strong (the same AA-contrast trade-off the panel made).
Dark mode uses --brand-600 as primary / --brand-400 as text-accent
for legibility on the zinc-950 surface.
* Zinc neutrals → --sl-color-gray-1..7 + page/surface/sidebar bgs.
* Semantic asides (:::note / :::tip / :::caution / :::danger) repaint
to the panel's --info / --success / --warning / --danger pairs +
matching soft fills. Light mode uses the explicit hex pairs from
theme.css (#2563eb/#eff6ff for info, etc.); dark mode mirrors the
rgb(<hex> / 0.4) recipe theme.css lines 97-101 use.
* Geometry — radii (--sbpp-radius-{sm,md,lg,xl}) + shadow scales +
the focus-ring outline (`2px solid var(--sl-color-accent);
outline-offset: 2px`) ported across so cards / code blocks /
buttons / asides have the same shape language as the panel.
* color-scheme: light / color-scheme: dark mirrored from theme.css
line 44 / 89 (the #1309 declaration that lets browsers paint
native UA surfaces — open <select> panels, scrollbars, autofill,
date pickers — in the matching scheme).
* ::selection paints orange in both modes so the brand is visible
even on incidental highlight.
The override deliberately skips font wiring (per #1333 §2 — color
identity carries the "one product" feel; matching fonts adds asset-
sharing complexity for diminishing return). The panel ships
vendored Inter + JetBrains Mono; Starlight stays on its defaults.
The header comment + AGENTS.md row added in a later commit are how
future maintainers know to mirror panel-side changes here.
Workstreams 3 + 4 of the migration. The two are entangled — the
tabs/asides/cross-link landings ARE the content port for the pages
that use them — so they ship together.
13 source pages converted from Hugo TOML front matter to Starlight
YAML, plus a landing page (index.mdx, splash template) and the
legacy soft-archive landing (workstream 5). Final layout:
* getting-started/quickstart.mdx ← quickstart.md (TABS)
* getting-started/prerequisites.mdx ← extracted §Prerequisites + new (TABS)
* setup/adding-server.md ← adding_server.md
* setup/plugin-setup.md ← new (was a "see Quickstart" stub)
* setup/ports.md ← ports.md
* setup/mariadb.mdx ← mariadb.md (TABS)
* updating/index.md ← updating.md
* troubleshooting/browser-freeze.md ← browser_freeze.md
* troubleshooting/could-not-find-driver.md ← could_not_find_driver.md
* troubleshooting/database-errors.md ← database_errors.md
* troubleshooting/debugging-connection.md ← debugging_connection.md
* integrations/discord-forward-setup.md ← discord_forward_setup.md
* customization/removing-default-message.md ← removing_default_message.md
* customization/translating.md ← translating.md
* faq/index.md ← faq/_index.md
* faq/inquiries.md ← inquiries.md (placed under FAQ
per the issue's "your judgment"
— both are Q&A-shaped and FAQ
is the more discoverable home)
* legacy/index.md ← workstream 5 (caution-banner stub)
* index.mdx ← new landing (splash template)
Per-page port rules applied (workstream 3):
* TOML +++…+++ → YAML ---…---. Dropped: bref (became `description`
when not already set), weight (→ sidebar.order), toc, draft,
translationKey.
* HTML-in-Markdown swept: <mark>X</mark> → `X` (inline code) for
placeholder values (Server Hostname, RCON Password, etc.);
<samp>X</samp> → `X` (inline code) for example URLs and
command names; <a target="_blank_"> stripped (Starlight handles
external link affordances).
* `> Note:` blockquotes promoted to Starlight asides:
- 1.6.x warning in quickstart → :::caution
- Cloudflare/Rocket Loader note → :::caution
- Smarty 5 {php} → {load_template} → :::caution
- "you can…" suggestions → :::tip
- Discord forwarder web/in-game → :::note
* Internal links rewritten from /docs/foo to the new IA:
- /docs/quickstart → /getting-started/quickstart/
- /docs/mariadb → /setup/mariadb/ (+ #granting-permission)
- /docs/adding_server → /setup/adding-server/
- /docs/updating → /updating/
- /docs/inquiries → /faq/inquiries/
- {{< ref "mariadb.md#granting-permission" >}} ditto
- {{< ref "docs/quickstart.md" >}} ditto
UX folds (workstream 4):
* Tabs in quickstart.mdx (Linux/Windows/Shared/Docker) — chmod block
ONLY in the Linux tab per the issue.
* Tabs in mariadb.mdx (Ubuntu repo / official repo / MySQL) plus a
paired tabs block for the GRANT syntax (MariaDB-MySQL8+ vs MySQL-5.x).
* Tabs in prerequisites.mdx for the MariaDB vs MySQL DB-choice section.
* Cross-link asides on every step with a known failure mode:
- DB step → /troubleshooting/database-errors/ +
/troubleshooting/could-not-find-driver/
- SteamID format → :::tip with a tooling pointer
- Cloudflare/Rocket Loader → caution + link to
/troubleshooting/browser-freeze/
- 1.6.x preflight on quickstart → caution + link to /updating/
* "What's next" footer on quickstart.mdx — LinkCard grid pointing to
adding-server, plugin-setup, discord-forward-setup.
* Landing page (index.mdx) ships a splash hero + 4-card grid
(Quickstart / Adding Server / Troubleshooting / Updating).
* Code copy buttons inherited from Starlight default — no opt-out.
Other notes:
* PHP version reference is the current floor (>= 8.5, per
web/composer.json — AGENTS.md says PHP 8.5 floor). The
legacy 1.7.0 step in updating/index.md keeps the historical
">= 8.2" wording for context but the prerequisites page cites the
current floor.
* Inquiries kept under /faq/ rather than under Customization;
every entry is a Q&A and reads more naturally next to FAQ proper.
* `kv` (KeyValues) codeblock language tags swapped to `ini` for
Shiki compatibility — KeyValues is structurally close enough that
the highlighter doesn't get confused.
* docs/src/content.config.ts pinned to Starlight's stock docsLoader
+ docsSchema; no custom front-matter validators.
The legacy soft-archive lands as a single index.md (workstream 5);
specific upgrade-from-X content moves into legacy/ in future PRs as
old paths are documented.
Workstream 6. Three new workflows under .github/workflows/, each gated on docs/** path filters so they stay out of the way of PRs that don't touch the site. * docs-build.yml — runs `npm run build` on PRs + main pushes that touch docs/. Uploads the built dist/ as an artifact (7-day retention) so reviewers can poke at chrome locally without rebuilding. Falls back to `npm install` until the lockfile ships in a follow-up. * docs-deploy-trigger.yml — runs on main pushes only, fires a repository_dispatch (event_type=docs-changed) into sbpp.github.io via a GitHub App-minted token. Uses actions/create-github-app-token@v1 with vars.DOCS_DEPLOY_APP_ID + secrets.DOCS_DEPLOY_APP_KEY (one-time setup before the workflow can succeed; documented inline + in the PR description). Concurrency group=docs-deploy-trigger so coalesced pushes don't pile up dispatches. * docs-screenshots.yml — runs on `affects-ui`-labelled PRs + workflow_dispatch. Boots the dev stack via `docker compose up -d --wait`, runs `npm run capture` (lands in workstream 7), tears down via `docker compose down -v`, then auto-commits any PNG deltas back to the PR branch via stefanzweifel/git-auto-commit-action@v5. Uses pull_request_target so it can write to forks; pinned to the PR head SHA so a malicious PR can't swap the ref under us. STEAM_API_KEY is the hardcoded all-zero dummy from #1333 §7 (the dev seed never round-trips back to Steam). All three carry top-of-file comments explaining purpose, cadence, and any required repo-side configuration. The capture script and the AGENTS.md row that mentions the `affects-ui` label land in later commits.
Workstream 7. New docs/scripts/capture.mjs drives Playwright + Chromium against the local dev stack to grab installer + panel screenshots. Wired via `npm run capture` in docs/package.json (landed in the scaffold commit). Output paths: docs/src/assets/auto/install/install-NN-<step>.png docs/src/assets/auto/panel/panel-NN-<page>.png Stable filenames so docs pages can reference these paths once and not re-edit when a screenshot regenerates. The script defaults: PANEL_URL → http://localhost:8080 STEAM_API_KEY → 00000000000000000000000000000000 (#1333 §7) ADMIN_USER / PASS → admin / admin VIEWPORT → 1280×800 The route list ships as a SKELETON for the first PR — install steps 1-6 + five panel routes (login, dashboard, banlist, servers, admin-dashboard). Several routes carry inline TODOs because they need actual click-through driving (the wizard handoff between steps 2-4 + the login form for admin-only routes), and the shipped login routine here is a best-effort that falls through if the form doesn't paint as expected. This is intentional: the screenshot ergonomics aren't the load- bearing part of the migration, and `docs-screenshots.yml` runs against this script wholesale, so a follow-up PR can flesh out each route without touching the CI plumbing. Documentation for the prerequisites + run sequence lands in docs/README.md in the bookkeeping commit.
Workstream 8. Three additions to the existing AGENTS.md surface so agents working on the panel know to mirror their changes into docs/ in the same PR: * Five new rows in the "Keep the docs in sync" table covering the user-facing change axes the docs site is the source of truth for: install / upgrade / troubleshooting flows, config knobs (paired with UPGRADING.md when breaking), new self-hoster-visible features (with a sidebar entry in astro.config.mjs), the `affects-ui` label for screenshot-relevant PRs, and the panel-theme-token-mirror rule for web/themes/default/css/theme.css ↔ docs/src/styles/sbpp.css. * One new "Quick rule" bullet that elevates the same expectation to a top-level rule: docs ship in the same PR as the user-facing change, screenshots auto-capture, theme tokens mirror. * Two new "Where to find what" rows: one for editing / adding a docs page (covers the .md vs .mdx split, sidebar wiring, dev loop, CI gates, source-of-truth contract), one for the screenshot capture pipeline (Playwright, output paths, CI trigger, the all-zero STEAM_API_KEY). All wording verbatim from the issue body where the issue gave a template, otherwise hewing to the AGENTS.md voice (prescriptive, one-liner-by-default with the "where to look" pointer).
Workstream 9 of the migration. Onboarding doc for the new docs/ project — covers: * Where things live (per-directory map of the Astro + Starlight source tree, with the panel-parity contract called out for src/styles/sbpp.css). * Local dev loop (`cd docs && npm install && npm run dev`). * Capture story (`./sbpp.sh up && cd docs && npm run capture`), including the all-zero STEAM_API_KEY note from #1333 §7 and the PANEL_URL override for parallel-stack worktrees. * CI workflow table (docs-build, docs-deploy-trigger, docs-screenshots) with the one-time setup notes for the GitHub App + the `affects-ui` label. * Authoring conventions — .md vs .mdx split, prefer Markdown- native :::note over <Aside>, cross-link asides, absolute-path internal links, Shiki language names (KV → ini), heading-level contract for the auto-ToC. * Source-of-truth statement so contributors arriving from sbpp.github.io land in the right place. This is the bookkeeping commit. The PR description (drafted in the orchestrator's PR-open step) carries the equivalent of the "requires DOCS_DEPLOY_APP_ID/KEY + affects-ui label setup" notes for reviewers.
…ckfile (#1333) `npm run build` was failing in two places that only surface once the dependency tree is actually installed: 1. **`social` config shape.** Starlight 0.30 takes `social` as a `Record<KnownPlatform, url>` (see `node_modules/@astrojs/starlight/schemas/social.ts`); the `[{icon, label, href}]` array shape was added in a later minor. Swap the array literal for the record shape and pin the gotcha in a comment so a future `^0.30.0` → `^0.32.0` bump knows to re-flip it. 2. **MDX autolink.** `index.mdx` had `<https://sbpp.github.io/>` — plain Markdown's autolink syntax, but MDX parses `<...>` as a JSX tag and bails on the `/`. Swap to a labelled `[sbpp.github.io](https://sbpp.github.io/)` link. 3. **Commit the lockfile.** `npm install` generated a `package-lock.json` that the workflows (`docs-build.yml`, `docs-screenshots.yml`) explicitly check for to drop into the `npm ci` deterministic-install path. Without the lockfile the workflows fall back to `npm install` and emit a `::warning::` on every run; committing it now both makes CI quiet and pins the dep tree across environments. Verified locally: `npm run build` exits 0, builds 19 pages, Pagefind indexes 18 of them (the 404 doesn't index by design), sitemap-index.xml is generated, `astro check` reports 0 errors / 0 warnings / 0 hints.
…ted-capture (#1333; B1, B2, m2, m3 partial, m4) The original docs-screenshots.yml ran `pull_request_target` with `contents: write`, then checked out the PR head and `npm ci`'d it. Any contributor PR could exfil the upstream repo's GITHUB_TOKEN through a postinstall hook or a tweaked capture.mjs (B1). The auto-commit step also wouldn't have worked on fork PRs — wrong checkout shape (B2). Split into two workflows: - `docs-screenshots-build.yml` runs on every `pull_request` that touches the capture script or its manifests. No secrets, no write permissions, standard `pull_request` token. `npm ci` + `node --check` on the script: catches "did this change still parse" cheaply. - `docs-screenshots-capture.yml` does the actual stack boot + capture + auto-commit. Defenses: * Trusted code (capture.mjs + lockfile + node_modules) checked out from the PR's BASE branch, NOT the PR head. `npm ci` postinstall scripts always come from main. * PR head checked out into a separate `pr-head/` directory used only for `docker compose up` (sandboxed by docker) and as the screenshot output destination. No JS/PHP code from the PR head runs on the runner. * Gated on the `safe-to-screenshot` label (maintainer ack). * `unlabel-on-synchronize` job auto-strips the label on every push so a maintainer must re-apply after each commit set — a benign opening commit can't grant blanket consent for follow-ups. * Refuses fork PRs at the workflow `if:` level (canonical "compromise the upstream maintainer" shape). Push the branch upstream first if the contribution needs screenshot regen. * `git-auto-commit-action@v5` runs from `pr-head/` with the canonical fork-PR checkout shape (`repository:` + `ref: head_ref` + `persist-credentials: true`). The fork-rejection gate above means in practice we always push to the upstream repo, but the canonical shape means a future opt-in fork support change doesn't silently break the push (B2 fix). `capture.mjs` grows a `CAPTURE_OUT_OVERRIDE` env var so the trusted script (running in `trusted/docs/`) can write into the PR head's `docs/src/assets/auto/` directory. Default behavior unchanged for local runs. Also folds in `m2` (call `./sbpp.sh db-seed` between stack boot and capture so post-install panel routes screenshot the populated state), `m3 partial` (prune install routes to the three reachable cold — licence, db-details, admin-create — defer steps 3/4/6 to a follow-up that drives the POST handoff chain), and `m4` (drop the stale "needs login" TODO from `panel-05-admin-dashboard` — `loginAsAdmin` already runs before the panel routes). Updates `docs/README.md` (CI table + new "Screenshot capture security model" section) and `AGENTS.md` (rule + quick-rules bullet) to point at the new label name and the local-run-first guidance.
Three things wrong with the worker's first cut: - B3: the pre-1.5.4.7 plugin upgrade content was left in `updating/index.md` (lines 86-106 of the worker's diff) instead of being moved into `legacy/`. Issue #1333 §5 explicitly says: "The pre-1.7 / pre-1.8 upgrade paths from `updating.md` lines 51-60 move here." Moved the section to a new `legacy/plugin-pre-1.5.4.7.md` page; left a one-line "see Legacy → Plugin upgrade from <= 1.5.4.7" pointer in `updating/index.md` so users still find it. - B4: the worker's `astro.config.mjs` added the Legacy group to the main sidebar. Same §5 line: "Legacy section is excluded from the main sidebar but reachable from a footer link and from the live `Updating` page." Removed the sidebar group; left a comment pointing at the override + the inline updating link as the two surviving discovery paths. - B5: there was no chrome-level discovery hook for /legacy/ — the spec says "footer link". Added `src/components/Footer.astro` that re-renders Starlight's stock per-page footer (EditLink + LastUpdated + Pagination + the optional Built-with-Starlight kudos) and appends a small "Legacy docs" affordance below it. We re-export Starlight's stock sub-components from the `virtual:` namespace rather than re-implementing them so a Starlight upgrade that adds new footer affordances picks them up automatically. Wired into `astro.config.mjs` as `components: { Footer: ... }`. - m5: the legacy banner copy was a worker rewrite ("These pages cover legacy SourceBans++ versions that are no longer actively maintained..."). Replaced with the issue's verbatim wording: "Documentation in this section refers to versions that are no longer maintained. The information may be inaccurate or out of date." (#1333 §5). Removed the "will be pruned in a future cleanup pass" sentence to match. Updated the legacy index's "Pages" section to actually list the new `/legacy/plugin-pre-1.5.4.7/` page instead of saying "empty for now".
…#1333; M1, M7, m9) Two paint mismatches between docs and panel chrome, both load-bearing for the "feels like one product" goal: - M1: ThemeProvider's `else` branch (no localStorage entry yet) was setting theme to 'light'. The panel reads `localStorage.getItem('sbpp-theme') || 'system'` and resolves 'system' via `prefers-color-scheme` (web/themes/default/js/theme.js lines 19-30). Dark-OS user landed on docs LIGHT then panel DARK — cross-product mismatch. Flipped the default to 'auto' so both surfaces honor system preference on first paint. - m9: that fix also makes the previously-unreachable `'auto'` arm of the resolved-theme expression reachable, which is the whole point. - M7: JS-disabled visitors landed on dark unconditionally because Starlight 0.30 hardcodes `<html data-theme="dark">` SSR'd into every page (node_modules/@astrojs/starlight/components/Page.astro line 39), and the worker's ThemeProvider script is JS-only. The panel paints LIGHT for no-JS visitors (no `prefers-color-scheme` media query in `theme.css`), so docs and panel diverged here too. Added a `<noscript><style>` block to ThemeProvider.astro that re-applies the LIGHT-mode color tokens to `:root[data-theme='dark']` so the cascade lands on light when JS isn't available. The block duplicates the LIGHT token list from sbpp.css; the file's leading comment carries the "keep these in sync" rule. Updated `docs/README.md`'s "Where things live" table to describe the new dual behavior + flag the noscript-block-must-mirror-sbpp.css maintenance contract.
The integration was scaffolded into the initial migration commit but
zero docs pages actually use a fenced \`\`\`mermaid block:
rg '\`\`\`mermaid' docs/src/ # no matches
That's a 36KB integration + ~600KB Mermaid runtime as a Vite chunk
family + Dependabot churn for nothing the chrome currently exercises.
The reviewer caught this as M3.
When a docs page genuinely needs a diagram (likeliest place is the
upgrader / install flow per #1333 §1's "we already use diagrams in
ARCHITECTURE.md, consistency wins" framing), re-add the integration
in the same PR as the first \`\`\`mermaid codeblock so the bundle
weight has a paying customer. Don't add a stub mermaid block just to
justify it — that's a smell.
\`npm uninstall astro-mermaid\` removed 115 packages from the lockfile
(astro-mermaid + its mermaid + d3 / dagre / cytoscape / @braintree
and friends transitive tree). Removed the import + the integration
config block from astro.config.mjs and replaced it with a comment
that documents the rationale + the re-add story.
Two pages were authored from scratch by the migration worker without
being marked as such; reviewers couldn't tell which content was a
faithful port from the legacy Hugo site vs. invented text needing
extra scrutiny.
- M4: setup/plugin-setup.md is entirely new (the legacy
sbpp.github.io site had no dedicated plugin-setup page). The
worker also OMITTED sbpp_admcfg.smx from the companion plugin
list, even though game/addons/sourcemod/scripting/sbpp_admcfg.sp
ships in-tree. Added it (admin-config loader: "Reads Admin Files"
per the .sp myinfo block, replaces SourceMod's stock
admin-flatfile.smx). Added a top-of-page :::note flagging the
page as new content + pointing reviewers at the source tree as
the verification surface.
- M5: getting-started/prerequisites.mdx is mostly an extract of
Quickstart's prerequisites section, but it was authored from
scratch with extra framing the legacy source didn't have
(utf8mb4 paragraph, recovery-surface mention, an expanded
Composer paragraph). Added a top-of-page :::note flagging it as
new content + pointing at web/install/pages/page.3.php and
web/composer.json as the verification surface.
Spot-check pass over the prerequisites claims:
* PHP >= 8.5 — verified (composer.json line 30).
* memory_limit >= 64M — recommendation only; install wizard
doesn't actually check this. Reworded to call that out
explicitly.
* Extensions list — install wizard checks
`openssl, xml, gmp, pdo_mysql, mbstring` (page.3.php line 84).
Worker had `gmp / pdo_mysql / openssl / xml` and was missing
`mbstring`. Added it; the `php -m | grep` snippet now greps
for all five.
* `recovery.php` — verified (web/install/recovery.php exists
post-#1332). Reworded the paragraph to mention the file by
name so the description is grounded in something concrete.
The aside repaint at sbpp.css line 193 used `border-left` (physical property) where stock Starlight uses `border-inline-start` (logical, writing-direction-aware). Issue #1333 mentions LLM-driven translation in "Future automation goals", so RTL locales (Arabic, Hebrew) will land here eventually — and they would silently get the accent stripe on the visually-trailing edge if we shipped the physical property. One-line swap + a comment explaining why so a future override doesn't silently regress it.
The legacy Hugo source's chmod section had inline bullets describing why each path needs to be writable (`config.php file is writable`, `/demos folder is writable`, etc.). The worker collapsed all five chmods into a comment-less block, so a non-technical operator following the steps can't tell why they're chmod'ing each path — just that they should. Interleaved a one-line comment above each chmod line in the Linux tab block of `getting-started/quickstart.mdx` describing what the path is used for (config.php = wizard credentials, demos/ = player ban-evidence demos, themes_c/ = Smarty compiled-template cache, images/games/ = per-game admin-uploadable icons, images/maps/ = per-map thumbnails on the Servers list). Surface knowledge tax goes down without changing the actual commands the operator runs.
Cheap-wins basket from the reviewer's minor findings list: - M2: AGENTS.md row 5 paraphrased the spec — `see #1333 §2.` instead of the issue's verbatim `see #2.`. Reverted to the verbatim form per the reviewer's ask. - m1: docs-build.yml carried a "fall back to npm install if no lockfile" branch that's dead now that docs/package-lock.json is committed. Removed; `npm ci` is the deterministic path and will fail loudly if anyone bumps package.json without regenerating the lockfile (which is exactly the gate we want). - m6: .gitignore line 83 ignored docs/pnpm-lock.yaml without explanation. Dropped the line — we picked npm and an accidental pnpm lockfile slip should be a visible diff a reviewer can challenge, not silently swallowed. - m8: docs/README.md "Where things live" omitted `src/content.config.ts`. Added a row describing it as the Astro content-collection schema (re-exports Starlight's `docsLoader` + `docsSchema` so the `src/content/docs/` collection is wired with the right loader and front-matter validation). Two minor findings deliberately deferred: * m3 (partial — install routes 3/4/6 stay deferred): tracked in the inline comment above INSTALL_ROUTES in `docs/scripts/capture.mjs`. Driving the install wizard's POST handoff chain is a follow-up that lands when a real screenshot surface needs it; until then, the URL-only routes (1, 2, 5) are the three that actually paint cold. * m7 (inquiries FAQ "browse the docs" pointer): the worker's port already cross-links to specific troubleshooting / setup pages on every Q&A, so the generic "browse the docs" trailer would be a weaker version of the targeted links already present. Skipped.
…ild/check fixes)
Two follow-ups from running the verification gates:
- The new `docs/src/content/docs/legacy/plugin-pre-1.5.4.7.md` page
builds to `/legacy/plugin-pre-1547/` by default — Astro 5 strips
dots from filename-derived slugs, which loses the version intent
in the URL. Cross-links from updating/index.md and legacy/index.md
use the dotted form. Pinned the slug explicitly via frontmatter
(`slug: legacy/plugin-pre-1.5.4.7`) so the URL matches the
cross-links.
- `astro check` errored on the new `Footer.astro` override because
it imports `virtual:starlight/components/{EditLink,LastUpdated,
Pagination}` and Starlight ships those type declarations in an
unscoped `virtual-internal.d.ts` that isn't a public package
export entry. Added `docs/src/env.d.ts` with a triple-slash
reference path into the package so consumer overrides type-check
cleanly. Rerunning `npx astro check` now reports 0 errors / 0
warnings across all 7 files.
Merged
10 tasks
Rushaway
pushed a commit
to srcdslab/sourcebans-pp
that referenced
this pull request
May 15, 2026
… (with skip-when-unconfigured guard) (sbpp#1347) * ci(docs): skip dispatch when GitHub App not configured Adds a job-level `if: vars.DOCS_DEPLOY_APP_ID != ''` guard to `docs-deploy-trigger.yml` so the workflow no-ops cleanly until the cutover steps in sbpp#1333 step 2 land (App creation + `DOCS_DEPLOY_APP_ID` var + `DOCS_DEPLOY_APP_KEY` secret). Pre-fix, every push to `docs/**` after sbpp#1339 merged ran the dispatch job, hit `actions/create-github-app-token@v1` with an empty `app-id` input, and exited red with `Input required and not supplied: app-id` (run 25646867498). Operators landing unrelated docs PRs would now see a stream of red runs that look like real CI failures but are actually the expected "cutover not done yet" state — exactly the friction the "Required repo configuration" comment block was meant to warn about. The job-level `if:` is the canonical "feature-flag a job" shape: unset repo variables resolve to empty string in expressions, so the guard is transparent once the var lands and the job runs every push as designed. Skipped runs show up as a grey "Skipped" badge in the Actions tab — the right visual signal for "intentionally inert until configured". Comment block above the workflow expanded to call this out explicitly so future readers don't try to "fix" the guard by removing it. * ci(docs): switch dispatch trigger from GitHub App to fine-grained PAT Replaces the `actions/create-github-app-token@v1` round-trip and the App-side `vars.DOCS_DEPLOY_APP_ID` + `secrets.DOCS_DEPLOY_APP_KEY` pair with a direct `secrets.DOCS_DEPLOY_PAT` (a fine-grained PAT scoped to `Actions: Read and write` on `sbpp.github.io` only). The previous fix in this PR's first commit (`0bf26262`) gated the job on `vars.DOCS_DEPLOY_APP_ID`. Renamed the gate variable to `vars.DOCS_DEPLOY_ENABLED` since "App ID" is no longer the right mental model — the variable is now a pure feature-flag the operator sets to opt in once the PAT is configured. (We can't reference `secrets.*` directly in a job-level `if:` per GitHub Actions' context-availability rules, so the paired VARIABLE is the canonical "feature-flag a job" shape.) Why PAT over App: a fine-grained PAT scoped to one repo + one permission carries the same blast radius as the org-owned App but takes ~2 minutes to provision instead of ~10. Tradeoff is the PAT is tied to a personal account and has a max one-year expiry that needs rotation; for a project of this size that's a fair deal. Operator instructions live in the workflow's top-of-file comment block + `docs/README.md`'s workflow table. The job-level `if:` guard pattern is unchanged in shape — every push to `docs/**` shows up as "Skipped" (grey badge) until the operator sets BOTH `secrets.DOCS_DEPLOY_PAT` and `vars.DOCS_DEPLOY_ENABLED`. The deploy shell in `sbpp.github.io` also has a `workflow_dispatch` trigger as a manual fallback while the PAT is pending. * ci(docs): drop redundant DOCS_DEPLOY_ENABLED variable The previous commit on this branch added a paired `vars.DOCS_DEPLOY_ENABLED` feature flag because I'd convinced myself `secrets.*` was unavailable in any `if:` context. That's half-true: it's unavailable in JOB-level `if:` (which is what I was trying to use), but it IS available in STEP-level `if:`. Since the trigger job has only one step (the dispatch), gating that single step on `if: secrets.DOCS_DEPLOY_PAT != ''` is behaviorally equivalent to a job-level guard — the runner spins up briefly to evaluate the step, sees no credential, marks the step Skipped, and the run finishes green-with-skipped instead of red-failing. The "wasted runner spin" cost I cited as justification for the variable is real but tiny (a few seconds of CI minutes vs ~30s of job-startup overhead either way), and the green-with-skipped status is arguably MORE informative than a fully-skipped grey job — it tells you "I ran, had no work to do" rather than "I didn't run". The variable was the operator's "I've done the cutover" signal, but the secret being set IS that signal. One source of truth, one less thing to forget. Cutover is now: create PAT, paste into `secrets.DOCS_DEPLOY_PAT`, done. Comment block above the workflow + `docs/README.md` workflow table updated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1333.
Migrates the SourceBans++ docs from Hugo (separate
sbpp/sbpp.github.iorepo) to Astro + Starlight, with the source of truth landing underdocs/in this repo. The deploy shell atsbpp.github.iois updated in the sibling PR (cutover step 1 below).What ships in this PR (9 workstreams)
Scaffold —
docs/Astro project: Astro 5 + Starlight 0.30 + Pagefind (built into Starlight).tsconfig.jsonextendsastro/tsconfigs/strict. Brand mark reused verbatim fromweb/themes/default/images/favicon.svg(orange shield + cross). Dependabot has annpmecosystem entry fordocs/on the same monthly cadence as the existing composer entry.Theme —
docs/src/styles/sbpp.cssmirrorstheme.css's:rootandhtml.darkblocks token-for-token (brand-50…950, zinc-50…950, semantic asides info / success / warning / danger with their*-bgsoft fills, geometry, focus ring). Aside repaints wire:::note → info,:::tip → success,:::caution → warning,:::danger → danger.color-scheme: light/darkdeclared per scope. A customThemeProvider.astrodefaults the unset preference to'auto'(matching the panel'slocalStorage.getItem('sbpp-theme') || 'system'); a<noscript><style>block re-applies the LIGHT-mode tokens to:root[data-theme='dark']so JS-disabled visitors see the same first-paint as the panel.Content port — All 13 source pages from
sbpp.github.io/content/docs/plus the FAQ index (14 total) ported to YAML front matter + idiomatic Markdown. Hugo-isms stripped (bref/weight/toc/draft/translationKey).<mark>/<samp>→ backticks.> Note:→ Starlight asides./docs/foo→ new IA routes (kebab-case slugs). PHP version bumped to 8.5 in the new docs to match the currentweb/composer.jsonfloor (post-Bump minimum PHP version to 8.5 #1289).UX folds — Tabs on
quickstart(Linux / Windows / Shared hosting / Docker; chmod block only in the Linux tab, with per-path comments above eachchmodline explaining why each path needs to be writable). Tabs onsetup/mariadb(Ubuntu repo / official repo / MySQL, plus MariaDB-or-MySQL-8 vs MySQL-5.x). Tabs onprerequisites(MariaDB / MySQL). "What's next"<CardGrid>footer on quickstart linking toadding-server/plugin-setup/discord-forward-setup. Cross-link asides on every step that has a known failure mode.Legacy soft-archive —
legacy/index.mdcarries the issue's verbatim caution banner; the pre-1.5.4.7 plugin upgrade content moved intolegacy/plugin-pre-1.5.4.7.md(out ofupdating/index.md). Section is excluded from the main sidebar per spec; reachable via a footer link emitted by the newFooter.astrocomponent override.CI workflows — Three workflows under
.github/workflows/:docs-build.yml— push-to-main+ PR, paths-gated todocs/**, Node 20,npm ci, builds, uploadsdist/as artifact.docs-deploy-trigger.yml— push-to-mainonly. Mints installation token viaactions/create-github-app-token@v1and firesrepository_dispatch(event_type=docs-changed) intosbpp.github.iowithclient_payload[source_sha].docs-screenshots-build.yml(sandboxedpull_request) +docs-screenshots-capture.yml(label-gatedpull_request_targetthat refuses fork PRs and runs the capture script from the PR base branch — see security model below).Auto-capture script —
docs/scripts/capture.mjsis a runnable Playwright skeleton with the install routes that the URL-only approach actually reaches cold (1, 2, 5) plus 5 panel routes. Stable filename slugs. Hardcoded all-zeroSTEAM_API_KEY.npm run capturescript wired. Routes 3/4/6 of the install wizard need a POST-handoff drive (the wizard re-validates the prefix at the top of every later step and bounces direct deep-links back to step 2); follow-up issue.AGENTS.md — All 4 rows from Docs: migrate to Starlight and merge authoring into sourcebans-pp #1333 §8 added verbatim to the "Keep the docs in sync" table. The quick-rule bullet appended verbatim to "Quick rules".
Bookkeeping —
docs/README.mddocuments local dev, build, capture, the workflows in a table, thesafe-to-screenshotlabel note +gh label createcommand, and authoring conventions.Review-fix changes (commits 9653c27 → 319b88e)
A second pass addressed every blocker and major from the adversarial review:
docs-screenshots.ymlranpull_request_targetwithcontents: write, thennpm ci'd PR-head code. Replaced with two workflows:docs-screenshots-build.yml(sandboxedpull_request, no secrets, no writes — verifies the script parses on every PR) anddocs-screenshots-capture.yml(gated on a maintainer-appliedsafe-to-screenshotlabel that auto-strips on every push, refuses fork PRs, runs the capture script from the PR base branch / writes outputs into the PR head). Seedocs/README.md"Screenshot capture security model" for the full threat model.legacy/plugin-pre-1.5.4.7.md; section excluded from the main sidebar; newFooter.astrooverride surfaces a "Legacy docs" affordance on every page.ThemeProvider.astrodefaults to'auto'(matching the panel's'system'default) AND ships a<noscript><style>block so JS-disabled visitors see light first paint, matching the panel's no-JS behavior.setup/plugin-setup.mdandgetting-started/prerequisites.mdxcarry top-of-page:::notebanners;sbpp_admcfg.smxadded to plugin list (was missing);mbstringadded to prerequisites extension list (the install wizard checks for it).border-left→border-inline-starton aside repaints (matters for the future LLM-translation pipeline).Cutover prerequisites (NOT in this PR)
These are repo-level config / org-level App setup that must land before the workflows are useful. Workflow top-of-file comments +
docs/README.mdall surface these.Sibling repo reset.
sbpp/sbpp.github.io@mainreset to a thin deploy shell +deploy.yml. See sibling PR ➜ sbpp/sbpp.github.io#NN (link below once filed). GitHub Pages source flipped from "Deploy from a branch" → "GitHub Actions" in the Pages settings.sbpp-docs-deployGitHub App. Org-owned, scopeActions: writeonsbpp.github.ioonly, installed onsbpp/sbpp.github.io. Repo VARIABLEDOCS_DEPLOY_APP_IDand repo SECRETDOCS_DEPLOY_APP_KEYset onsbpp/sourcebans-pp.Create the
safe-to-screenshotlabel on this repo sodocs-screenshots-capture.yml'sif:gate has a label to trigger on:Validation done
cd docs && npm install— succeeds; lockfile committed.cd docs && npm run build— exits 0, builds 20 pages, Pagefind indexes 19, sitemap-index.xml emitted.cd docs && npx astro check— 0 errors, 0 warnings, 0 hints.Known follow-ups (separate issues)
theme.cssandsbpp.css(per Docs: migrate to Starlight and merge authoring into sourcebans-pp #1333's "Future automation").socialback to the[{icon, label, href}]array form — the current record shape is a Starlight 0.30 quirk pinned in a comment inastro.config.mjs.