feat(web-app-sphinx): render the documentation under the design title

Sphinx bakes html_title into every static page at build time, so the tab, the header, the breadcrumbs and the mobile drawer kept the title from the image's conf.py. The role now builds a derived image from the pinned infinito-sphinx release that reruns sphinx-build with -D html_title set to the design title; without a title the Dockerfile is the bare FROM. The service is declared custom, so the image lookup resolves the local sphinx_custom tag, and compose gains the shared build include.

The spec asserts the title suffix in the tab, the header and the first breadcrumb on desktop and the drawer heading on mobile.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5.5 committed 2026-10-15 14:55:26 +02:00
1 parent 045c9a63f7
commit 35de016936
6 files changed
+30

No files matched your search

@@ -16,6 +16,7 @@ const base = normalizeBaseUrl(process.env.APP_BASE_URL || "");
const logoUrl = decodeDotenvQuotedValue(process.env.DESIGN_LOGO_URL || "");
const iconUrl = decodeDotenvQuotedValue(process.env.DESIGN_ICON_URL || "");
const faviconUrl = decodeDotenvQuotedValue(process.env.DESIGN_FAVICON_URL || "");
const designTitle = decodeDotenvQuotedValue(process.env.DESIGN_TITLE || "");
const MODES = ["light", "dark"];
const MAIN = "main";
@@ -320,6 +321,23 @@ test("design: logo, icon and favicon come from the design lookup", async ({ page
}
});
test("design: the documentation renders the configured title in the tab, header, breadcrumbs and drawer", async ({
page,
}) => {
skipUnlessServiceEnabled("design");
test.skip(!designTitle, "title replacement is disabled for this role");
const escaped = designTitle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
await page.setViewportSize({ width: 1440, height: 900 });
await open(page, ROLE_README);
await expect(page).toHaveTitle(new RegExp(`\\| ${escaped}$`));
await expect(page.locator("header a > span.font-bold")).toHaveText(designTitle);
await expect(page.locator(`${BREADCRUMBS} > a:first-child > span`)).toHaveText(designTitle);
await page.setViewportSize({ width: 390, height: 844 });
await open(page, "/");
await openSidebar(page);
await expect(page.locator(`${SIDEBAR} > a > span.font-bold`)).toHaveText(designTitle);
});
test("design: gallery of index, guides, role pages, generated pages, search, sidebar and switch states", async ({
page,
}) => {
+1
View File
@@ -26,6 +26,7 @@ sphinx:
enabled: true
image: ghcr.io/kevinveenbirkenbach/infinito-sphinx
version: v1.0.70
custom: true
min_storage: 1931MB
backup:
no_stop_required: true
@@ -0,0 +1,4 @@
FROM {{ SPHINX_IMAGE }}:{{ SPHINX_VERSION }}
{% if SPHINX_DESIGN_TITLE | length > 0 %}
RUN sphinx-build -M html "${INFINITO_SRC_DIR}" "${SPHINX_OUTPUT_DIR}" -c "${SPHINX_EXEC_DIR}src/infinito_docs" -D html_title={{ SPHINX_DESIGN_TITLE | quote }}
{% endif %}
@@ -4,6 +4,7 @@
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
{{ lookup('container_image', application_id, 'sphinx') }}
{{ lookup('compose_only', 'container_name', SPHINX_CONTAINER) }}
{{ lookup('template', 'roles/sys-svc-container/templates/build.yml.j2') | indent(4) }}
{{ lookup('container_ports', ['sphinx', 'http', DOCKER_BIND_HOST]) | indent(4) }}
{{ lookup('container_healthcheck', service_name) | indent(4) }}
{{ lookup('container_networks') | indent(4) }}
@@ -10,6 +10,8 @@ DESIGN_LOGO_URL={{ (lookup('design', application_id).urls['sidebar-logo'].svg if
DESIGN_SERVICE_ENABLED={{ lookup('config', application_id, 'services.design.enabled') | string | lower }}
DESIGN_TITLE={{ (lookup('design', application_id).title if lookup('design', application_id).title is string else '') | dotenv_quote }}
# nocheck: playwright-service-gate — covered by sys-svc-mail integration tests, not the persona surface
EMAIL_SERVICE_ENABLED={{ lookup('config', application_id, 'services.email.enabled') | string | lower }}
+4
View File
@@ -3,3 +3,7 @@ application_id: "web-app-sphinx"
entity_name: "{{ application_id | get_entity_name }}"
SPHINX_CONTAINER: "{{ lookup('config', application_id, 'services.' ~ entity_name ~ '.name') }}"
SPHINX_IMAGE: "{{ lookup('config', application_id, 'services.' ~ entity_name ~ '.image') }}"
SPHINX_VERSION: "{{ lookup('config', application_id, 'services.' ~ entity_name ~ '.version') }}"
SPHINX_DESIGN_TITLE: "{{ lookup('design', application_id).title if (lookup('config', application_id, 'services.design.enabled') | bool) and lookup('design', application_id).title
is string else '' }}"