From 13f9f4d98c2993d62c3bf8e5746de3991472e278 Mon Sep 17 00:00:00 2001 From: sehkone Date: Sat, 8 Aug 2026 15:51:13 +0900 Subject: [PATCH] Ship the ClumL company mark instead of a product logo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shared/brand.svg was the Clumit Security wordmark — one consumer's product logo, shipped to every consumer. aimer-web documents a different product and already carried its own copy, which an audit read as drift; it was not. It was a consumer refusing an asset that did not belong to it. bootroot and agentcoop document neither product, so adopting the theme would have put another product's branding on their manuals, and bootroot's site is public. One file cannot serve the three places a mark appears, so there are three. brand.svg has white lettering for the header, which sits on the primary-coloured bar. brand-print.svg has black lettering for the PDF cover, which sits on white paper. brand-symbol.svg is the cube alone, because a wordmark scaled to a 16-pixel tab icon is unreadable. The PDF script had to move to brand-print.svg in the same change: it read brand.svg, which now carries white lettering, so leaving it would have rendered an invisible logo on white paper with nothing downstream reporting it. Vendoring took away the way aimer-web and aice-web-next keep their product marks — docs/theme/ is covered by the .meta digest and an edit there is reverted. theme.logo and theme.favicon already work, because mappings merge recursively on INHERIT. The PDF cover did not, so extra.pdf.cover_logo is added. A path that does not resolve is an error rather than a silent fall back to the company mark, which would ship a cover branded with the wrong name. Closes #38 --- CHANGELOG.md | 34 +++++++++++++ README.md | 37 +++++++++++++- scripts/build-docs-pdf.sh | 21 ++++++-- scripts/fetch-theme.sh | 14 ++++-- shared/brand-print.svg | 1 + shared/brand-symbol.svg | 1 + shared/brand.svg | 41 +-------------- templates/api-reference/mkdocs-base.yml | 2 +- templates/manual/mkdocs-base.yml | 2 +- tests/installer-test.sh | 2 + tests/pdf-test.sh | 66 +++++++++++++++++++++++++ 11 files changed, 171 insertions(+), 50 deletions(-) create mode 100644 shared/brand-print.svg create mode 100644 shared/brand-symbol.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index fe920b2..aa4560e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,40 @@ Keep a Changelog change-type headings, so a consumer reads only the sections that affect its template. Version numbers follow the `MAJOR.MINOR.PATCH` policy described in [the README](README.md#versioning). +## Unreleased + +### shared + +- The brand assets are the **ClumL company mark**, replacing a + `brand.svg` that was the Clumit Security product logo — one consumer's + product, shipped to every consumer. A project documenting a different + product would have published another product's branding. +- `brand.svg` now carries **white** lettering, for the site header, which + sits on the primary-coloured bar. Anything rendering it on a light + background needs `brand-print.svg` instead. +- Added `brand-print.svg`, the black-lettering variant the PDF cover + uses, and `brand-symbol.svg`, the cube alone. A wordmark scaled to a + 16-pixel tab icon is unreadable; the cube is not. + +### manual + +- `theme.favicon` points at `theme/brand-symbol.svg` rather than the + wordmark. + +### api-reference + +- `theme.favicon` points at `theme/brand-symbol.svg` rather than the + wordmark. + +### scripts + +- `fetch-theme.sh` installs all three brand assets. +- `build-docs-pdf.sh` takes the cover logo from `brand-print.svg`, and + accepts `extra.pdf.cover_logo` — a path relative to `docs_dir` — so a + project documenting a product with its own mark can put it on the + cover. A path that does not resolve is an error rather than a silent + fall back to the company mark. + ## [0.2.0] - 2026-08-08 ### manual diff --git a/README.md b/README.md index c991994..6cf2013 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ templates/ Template assets grouped by document type release-notes/ (planned) shared/ Assets shared across all templates fonts/ Roboto and Pretendard web fonts - brand.svg Brand logo + brand.svg ClumL wordmark, white lettering (site header) + brand-print.svg ClumL wordmark, black lettering (PDF cover) + brand-symbol.svg ClumL cube alone (favicon) styles/ Base CSS samples/ Per-template sample sites for previewing index.html Landing page linking to all samples @@ -190,6 +192,8 @@ It downloads the release named in `docs/theme.toml` and installs: | `shared/styles/base.css` | `docs/theme/styles/base.css` | | `shared/fonts/` | `docs/theme/fonts/` | | `shared/brand.svg` | `docs/theme/brand.svg` | +| `shared/brand-print.svg` | `docs/theme/brand-print.svg` | +| `shared/brand-symbol.svg` | `docs/theme/brand-symbol.svg` | | `scripts/build-docs-pdf.sh` | `docs/theme/build-docs-pdf.sh` | The install directory must not be dot-prefixed: MkDocs drops every @@ -253,6 +257,37 @@ default, and a consumer that needs its own `i18n` languages redefines the whole `plugins` list (which replaces the default wholesale), as the sample sites do. +### Branding + +The theme ships the **ClumL company mark** in three variants, because one +file cannot serve all three places. `brand.svg` has white lettering for +the site header, which sits on the primary-coloured bar. `brand-print.svg` +has black lettering for the PDF cover, which sits on white paper. +`brand-symbol.svg` is the cube alone: a wordmark scaled to a 16-pixel tab +icon is unreadable, and the cube is not. The base wires all three, so a +project documenting something ClumL publishes needs no branding config at +all. + +A project documenting a product with its own mark overrides them. Commit +the product's assets under `docs/` — **not** under `docs/theme/`, which +is covered by the `.meta` digest and reverted on the next installer run: + +```yaml +theme: + logo: assets/product-logo.svg + favicon: assets/product-symbol.svg + +extra: + pdf: + cover_logo: assets/product-logo-print.svg +``` + +`theme` is a mapping, so those two keys merge into the base rather than +replacing it — the palette, fonts, and features it defines all survive. +`extra.pdf.cover_logo` is a path relative to `docs_dir`; a path that does +not resolve is an error rather than a silent fall back to the company +mark, which would otherwise ship a cover branded with the wrong name. + The base already wires `extra_css` to the installed stylesheets (base, lists, and PDF guardrails, plus `api.css` for the api-reference template), so you do not list them yourself. To review the full set of diff --git a/scripts/build-docs-pdf.sh b/scripts/build-docs-pdf.sh index c036751..03b66a0 100755 --- a/scripts/build-docs-pdf.sh +++ b/scripts/build-docs-pdf.sh @@ -303,9 +303,24 @@ if cover_subtitle is not None: if toc_title is not None: options["toc_title"] = toc_title -brand_svg = os.path.join(theme_dir, "brand.svg") -if os.path.isfile(brand_svg): - options["cover_logo"] = brand_svg +# The cover sits on white paper, so it needs the black-lettering variant. +# theme/brand.svg is the white-lettering one the site header uses; putting +# that on the cover renders an invisible logo and nothing downstream would +# report it. A consumer documenting a product with its own mark points +# extra.pdf.cover_logo at it, relative to docs_dir. +cover_logo = text("cover_logo") +if cover_logo is not None: + docs_dir = str(data.get("docs_dir") or "docs") + cover_logo = os.path.join(root, docs_dir, cover_logo) + if not os.path.isfile(cover_logo): + fail(f"{config_path}: 'extra.pdf.cover_logo' does not exist at " + f"{cover_logo}") +else: + cover_logo = os.path.join(theme_dir, "brand-print.svg") + if not os.path.isfile(cover_logo): + fail(f"{cover_logo} is missing; the installed theme is incomplete. " + "Re-run ./scripts/fetch-theme.sh.") +options["cover_logo"] = cover_logo # cover_tagline is not a mkdocs-with-pdf option. The plugin seeds the # cover template context from `extra` and never overwrites this name, so diff --git a/scripts/fetch-theme.sh b/scripts/fetch-theme.sh index 2d12872..f597694 100755 --- a/scripts/fetch-theme.sh +++ b/scripts/fetch-theme.sh @@ -285,10 +285,16 @@ def stage(src, staging, template, template_dir, label): fail(f"shared/fonts/ not found in {label}") copy_tree(shared_fonts, os.path.join(staging, "fonts")) - brand = os.path.join(src, "shared", "brand.svg") - if not os.path.isfile(brand): - fail(f"shared/brand.svg not found in {label}") - copy_file(brand, os.path.join(staging, "brand.svg")) + # brand.svg carries white lettering for the site header, which sits on + # the primary-coloured bar; brand-print.svg is the black-lettering + # variant the PDF cover needs on white paper; brand-symbol.svg is the + # cube alone, which is the only one of the three that stays legible at + # favicon size. + for name in ("brand.svg", "brand-print.svg", "brand-symbol.svg"): + asset = os.path.join(src, "shared", name) + if not os.path.isfile(asset): + fail(f"shared/{name} not found in {label}") + copy_file(asset, os.path.join(staging, name)) pdf_script = os.path.join(src, "scripts", "build-docs-pdf.sh") if not os.path.isfile(pdf_script): diff --git a/shared/brand-print.svg b/shared/brand-print.svg new file mode 100644 index 0000000..eb8de33 --- /dev/null +++ b/shared/brand-print.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/shared/brand-symbol.svg b/shared/brand-symbol.svg new file mode 100644 index 0000000..cdb533c --- /dev/null +++ b/shared/brand-symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/shared/brand.svg b/shared/brand.svg index 3e05650..aced5e5 100644 --- a/shared/brand.svg +++ b/shared/brand.svg @@ -1,40 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/templates/api-reference/mkdocs-base.yml b/templates/api-reference/mkdocs-base.yml index 4009e5b..274cba6 100644 --- a/templates/api-reference/mkdocs-base.yml +++ b/templates/api-reference/mkdocs-base.yml @@ -27,7 +27,7 @@ theme: language: en font: false logo: theme/brand.svg - favicon: theme/brand.svg + favicon: theme/brand-symbol.svg features: - navigation.tabs - navigation.sections diff --git a/templates/manual/mkdocs-base.yml b/templates/manual/mkdocs-base.yml index 0ab55c5..357e5cf 100644 --- a/templates/manual/mkdocs-base.yml +++ b/templates/manual/mkdocs-base.yml @@ -27,7 +27,7 @@ theme: language: en font: false logo: theme/brand.svg - favicon: theme/brand.svg + favicon: theme/brand-symbol.svg features: - navigation.tabs - navigation.sections diff --git a/tests/installer-test.sh b/tests/installer-test.sh index aa3db0c..8bbdca7 100755 --- a/tests/installer-test.sh +++ b/tests/installer-test.sh @@ -87,6 +87,8 @@ for entry in \ fonts/Roboto-Regular.woff2 \ fonts/Pretendard-Regular.woff2 \ brand.svg \ + brand-print.svg \ + brand-symbol.svg \ build-docs-pdf.sh \ .meta do diff --git a/tests/pdf-test.sh b/tests/pdf-test.sh index e662e02..e20c349 100755 --- a/tests/pdf-test.sh +++ b/tests/pdf-test.sh @@ -711,4 +711,70 @@ contains "$WORK/tables.txt" "overlong-end" "a row taller than a page" contains "$WORK/tables.txt" "after-overlong" "the row after an overlong one" ok "a row taller than a page splits rather than losing its content" +# --- the cover logo ----------------------------------------------------- + +# The cover sits on white paper, so it must carry the black-lettering +# brand-print.svg. theme/brand.svg is the white-lettering variant the +# site header uses; rendering that on the cover produces an invisible +# logo, which is exactly the kind of silent asset loss nothing else here +# would catch. +generated="$(debug_build_pdf "$full" en)" +grep -q 'cover_logo:.*brand-print\.svg' "$generated" \ + || die "the cover did not default to brand-print.svg" +grep -q 'cover_logo:.*brand\.svg' "$generated" \ + && die "the cover used the white-lettering header logo" +ok "the cover logo defaults to the print variant, not the header one" + +# A consumer documenting a product with its own mark overrides it. The +# fixture carries a element so the substitution is observable in +# the rendered page rather than only in the generated config. +override="$WORK/override" +new_project "$override" +cat > "$override/docs/product-logo.svg" <<'SVG' +PRODUCTLOGOMARK +SVG +cat > "$override/mkdocs.yml" <<'EOF' +site_name: Override Fixture +extra: + pdf: + cover_logo: product-logo.svg +EOF +build_pdf "$override" en +pdf_text "$override/site/pdf/override-fixture.en.pdf" > "$WORK/override.txt" +contains "$WORK/override.txt" "PRODUCTLOGOMARK" "the overridden cover logo" +ok "extra.pdf.cover_logo puts a consumer's own mark on the cover" + +# A path that does not resolve is an error. Falling back to the theme +# logo would hand a consumer a cover branded with the wrong company. +cat > "$override/mkdocs.missing.yml" <<'EOF' +site_name: Missing Logo Fixture +extra: + pdf: + cover_logo: does-not-exist.svg +EOF +if (cd "$override" && ./docs/theme/build-docs-pdf.sh en mkdocs.missing.yml) \ + > "$WORK/missing-logo.log" 2>&1 +then + die "a cover_logo that does not exist should be an error" +fi +grep -q "extra.pdf.cover_logo" "$WORK/missing-logo.log" \ + || die "the error does not name extra.pdf.cover_logo" +ok "a cover_logo that does not resolve fails instead of falling back" + +# An incomplete install is an error for the same reason. This needs a +# config without an override, or the override would satisfy the cover and +# the missing asset would go unnoticed. +cat > "$override/mkdocs.default.yml" <<'EOF' +site_name: Default Logo Fixture +EOF +rm -f "$override/docs/theme/brand-print.svg" +if (cd "$override" && ./docs/theme/build-docs-pdf.sh en mkdocs.default.yml) \ + > "$WORK/no-print-logo.log" 2>&1 +then + die "a missing brand-print.svg should be an error" +fi +grep -q "brand-print.svg" "$WORK/no-print-logo.log" \ + || die "the error does not name the missing asset" +ok "a missing brand-print.svg fails instead of rendering a logo-less cover" + echo "All PDF script checks passed."