Skip to content

Let hosts brand the modern design with a theme.css - #874

Open
HyakoV3 wants to merge 2 commits into
librespeed:masterfrom
HyakoV3:theme-host
Open

HyakoV3 wants to merge 2 commits into
librespeed:masterfrom
HyakoV3:theme-host

Conversation

@HyakoV3

@HyakoV3 HyakoV3 commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Branding of the modern design for hosts

Follow-up to the suggestion of @sstidl in #863: a customization tool for people who host a frontend, as a static style
baked into the container or the frontend directory, with no switch for the visitors, and kept apart from the
localization (#873).

What it does

  • frontend/styling/theme.css is new, ships empty (only a comment) and is loaded after index.css on the modern
    page (index-modern.html and frontend/index.html). The default look does not change.
  • A host overrides the CSS variables that already exist in styling/colors.css, and can set its own background image
    and logo, by replacing or mounting that file. No JavaScript, no storage, no new setting.
  • frontend/README.md (section "Theming") lists the variables, how to change the background and the logo, and what a
    theme cannot reach: the colors inside the SVG files, the theme-color of the page and of the manifest, and the image
    made for sharing a result. It also notes that two variables of colors.css (--gauge-progress-color and
    --popup-shadow-color) are declared but not used, so changing them has no effect.
  • doc_docker.md explains the Docker paths.

Docker

The entrypoint empties /var/www/html and copies /speedtest/frontend into it at every start. A theme therefore has
to be mounted at /speedtest/frontend/styling/theme.css (or copied there in a derived image), not at
/var/www/html/frontend/...: mounting there makes the start fail (rm: can't remove ...: Resource busy, with
set -e), and the copy would overwrite it anyway. The modern design is served only with USE_NEW_DESIGN=true, and a
change to a mounted file needs a restart.

docker run -d -p 80:8080 -e MODE=standalone -e USE_NEW_DESIGN=true \
  -v $PWD/theme.css:/speedtest/frontend/styling/theme.css:ro \
  ghcr.io/librespeed/speedtest

Scope, on purpose

Only the modern design. The classic page and the stability page have their own styles. No environment variables
(THEME_*): they would need a sed in the entrypoint, and a mounted file or a COPY in a derived image already
covers "baked into the container or the frontend dir".

@sstidl, is a theme file that the host mounts or copies in a derived image what you had in mind, or would you rather
have a few ready-made themes picked by an environment variable?

Tests

tests/e2e/theme.spec.js: the shipped file is empty and keeps the default look; a host theme changes the accent color
and the background; a relative url() in theme.css is relative to the file; both modern pages load it after
index.css; and a theme mounted in the Docker image (standalone-apostrophe service, one volume and a fixture) is the
one that is served. The existing title-special-chars, modes, design-switch, static-repository-assets and
mobile-gauge-visibility specs pass with the Docker setup (23 tests).

frontend/styling/theme.css ships empty and is loaded after index.css on the modern
page, so a host can override the CSS variables of colors.css, the background and
the logo without touching the files of the project. There is no switch for the
visitors. The README documents the variables, what a theme cannot reach, and the
Docker paths: the entrypoint empties /var/www/html and copies /speedtest/frontend
at every start, so a theme is mounted (or copied) into /speedtest/frontend.

Follows the suggestion in librespeed#863 of a static style for hosts, kept apart from the
localization.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Add host-controlled CSS theming for the modern design

✨ Enhancement 📝 Documentation 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Add an empty theme stylesheet after modern-page styles, enabling host branding without changing
 defaults.
• Document CSS overrides, asset replacement, limitations, and the correct Docker mount path.
• Test default styling, host overrides, asset URLs, stylesheet order, and Docker delivery.
Diagram

graph TD
  A["Host theme.css"] -->|mounted into| B["Frontend source"] -->|copied by| C["Docker entrypoint"] --> D[("Web root")] --> E["Modern pages"] --> F["Browser"]
  G["Base stylesheet"] -->|loaded first| E
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Generate themes from environment variables
  • ➕ Allows simple color changes through container configuration.
  • ➖ Requires entrypoint transformation and limits customization to predefined options.
  • ➖ Adds deployment logic for a feature CSS already supports.

Recommendation: Keep the static theme.css override. It supports arbitrary CSS and asset paths without new runtime logic; environment-generated themes would add complexity while covering fewer branding needs.

Files changed (9) +157 / -0

Enhancement (3) +9 / -0
index.htmlLoad host theme in the standalone modern frontend +1/-0

Load host theme in the standalone modern frontend

• Links theme.css after index.css so host CSS can override the default styling.

frontend/index.html

theme.cssProvide an empty host theme override file +7/-0

Provide an empty host theme override file

• Adds a comment-only stylesheet that hosts can replace or mount without changing the shipped appearance.

frontend/styling/theme.css

index-modern.htmlLoad host theme in the repository-root modern page +1/-0

Load host theme in the repository-root modern page

• Links the frontend theme stylesheet after index.css on the root modern entry point.

index-modern.html

Tests (3) +79 / -0
theme-host.cssSupply a distinctive test theme +8/-0

Supply a distinctive test theme

• Defines accent and background overrides used by browser and Docker theming tests.

tests/e2e/fixtures/theme-host.css

static-repository-assets.spec.jsRequire theme.css among served repository assets +1/-0

Require theme.css among served repository assets

• Adds the new stylesheet to the expected static asset list.

tests/e2e/static-repository-assets.spec.js

theme.spec.jsCover modern theming and Docker delivery +70/-0

Cover modern theming and Docker delivery

• Checks unchanged defaults, CSS overrides, relative background URLs, stylesheet order on both modern pages, and the theme served from a Docker mount.

tests/e2e/theme.spec.js

Documentation (2) +66 / -0
doc_docker.mdDocument container paths for modern branding +8/-0

Document container paths for modern branding

• Explains where to mount or copy a theme and logo, why the web root is unsuitable, and how to enable the modern design.

doc_docker.md

README.mdAdd modern-design theming guide +58/-0

Add modern-design theming guide

• Documents CSS variables, background and logo customization, theme limitations, and Docker mount and derived-image examples.

frontend/README.md

Other (1) +3 / -0
docker-compose-playwright.ymlMount a host theme in the Docker test service +3/-0

Mount a host theme in the Docker test service

• Mounts the test theme into the frontend source directory of the standalone-apostrophe service to exercise startup copying.

tests/docker-compose-playwright.yml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. The new theme test can race container startup ✓ Resolved
Description
The new mounted-theme test navigates to standaloneApostrophe, but global setup never waits for
that container to become HTTP-ready. On a slow startup, the test can reach port 18186 before the
service accepts requests and fail at page.goto().
Code

tests/e2e/theme.spec.js[R65-66]

+  test("a theme.css mounted in the Docker image is the one that is served", async ({ page }) => {
+    await page.goto(`${baseUrls.standaloneApostrophe}/index-modern.html`);
Evidence
Global setup starts Compose in detached mode and waits for several HTTP endpoints, but not port
18186. The new test unconditionally navigates to that port; an existing title test also uses it, so
the new test adds another consumer of this readiness gap.

tests/e2e/global-setup.js[54-75]
tests/docker-compose-playwright.yml[47-58]
tests/e2e/theme.spec.js[65-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new theme test uses a Docker service that global setup does not wait for, so navigation can race startup.
## Fix Focus Areas
- tests/e2e/global-setup.js[67-75]
- tests/e2e/theme.spec.js[65-68]
## Recommended Fix
Add a readiness check for the standalone-apostrophe service at port 18186 to global setup before Playwright runs the tests.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Hosts cannot theme server option text ✓ Resolved
Description
The new variable guide points hosts to colors.css and --server-selector-* for the server
selector, but its option text uses the undeclared --sprint-text-color. Changing the documented
selector variables therefore leaves that text color unchanged, and hosts must discover a variable
the guide omits.
Code

frontend/README.md[R39-42]

+The variables that change the look are the ones in `styling/colors.css`: the accent colors (`--theme-green`,
+`--theme-pink`), the texts (`--primary-text-color`, `--secondary-text-color`, `--tagline-text-color`, and the disabled
+ones), the background (`--background-backup-color`, `--background-overlay-color`), the start button
+(`--button-*`), the server selector (`--server-selector-*`), the gauges (`--gauge-background-color`,
Evidence
The option link consumes --sprint-text-color; colors.css does not declare it, while the new
README presents that file and the selector-prefixed variables as the host's palette.

frontend/styling/server-selector.css[137-147]
frontend/styling/colors.css[1-36]
frontend/README.md[39-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new theming guide cannot tell hosts how to control server option text: its CSS consumes a variable absent from the documented palette.
## Fix Focus Areas
- frontend/README.md[39-45]
- frontend/styling/colors.css[1-36]
- frontend/styling/server-selector.css[141-147]
## Recommended Fix
Give the server option text a declared variable in colors.css with the existing default appearance, then include that variable in the theming guide.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Hosts are told the modern page is gated ✓ Resolved
Description
frontend/README.md says the modern design is only served when USE_NEW_DESIGN=true, although that
setting controls the default design selection rather than access to the page. Hosts leaving the
default setting unchanged can still reach it directly or through ?design=new, contrary to the new
Docker guidance.
Code

frontend/README.md[R76-77]

+The modern design is only served when `USE_NEW_DESIGN=true`. A change to a mounted file needs a restart of the
+container, because the files are copied at start.
Evidence
The design switch handles ?design=new before consulting configuration, and the Docker
documentation already describes URL parameters as an alternative to the default setting.

design-switch.js[18-34]
doc_docker.md[60-63]
frontend/README.md[76-77]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Docker theming instructions present the default-design setting as a requirement for serving the modern page.
## Fix Focus Areas
- frontend/README.md[76-77]
- doc_docker.md[103-105]
## Recommended Fix
Say that USE_NEW_DESIGN=true makes the modern page the default; note that direct access and the design URL parameter still work without it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread tests/e2e/theme.spec.js
Comment thread frontend/README.md Outdated
Comment thread frontend/README.md Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant