refactor(theme): single-source the HealthChat Light theme

Author the theme once in apps/web/element.io/healthchat/HealthChat-Light-Theme.json
and sync it into the web (apps/web/config.json) and desktop
(apps/desktop/element.io/healthchat/config.json) configs via
scripts/healthchat-sync-theme.mjs, removing the drifted duplicate.
`pnpm healthchat:theme` writes it; `pnpm lint` runs healthchat:theme:check to
fail on drift.

The canonical palette is the reviewed desktop theme, which reverts the web-only
contrast tweak (f64923ea1a) so web and desktop match.
This commit is contained in:
2026-07-14 14:02:39 +02:00
parent ce7f9230c3
commit f33708c705
6 changed files with 179 additions and 73 deletions
+18 -30
View File
@@ -8,21 +8,14 @@
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"default_country_code": "GB",
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.healthchat.ch",
"server_name": "healthchat.ch"
},
"m.identity_server": {
"base_url": ""
}
"m.homeserver": { "base_url": "https://matrix.healthchat.ch", "server_name": "healthchat.ch" },
"m.identity_server": { "base_url": "" }
},
"default_theme": "custom-HealthChat Light",
"disable_custom_urls": true,
"disable_guests": true,
"element_call": {},
"embedded_pages": {
"homeUrl": ""
},
"embedded_pages": { "homeUrl": "" },
"enable_presence_by_hs_url": "",
"features": {},
"help_url": "https://www.healthchat.ch",
@@ -30,19 +23,11 @@
"integrations_rest_url": "https://scalar.vector.im/api",
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_widgets_urls": ["https://scalar.vector.im/api"],
"jitsi": {
"preferred_domain": "jitsi.healthchat.ch"
},
"jitsi": { "preferred_domain": "jitsi.healthchat.ch" },
"logout_redirect_url": "",
"matrix_to_url": "https://to.healthchat.ch",
"room_directory": {
"servers": ["matrix.org"]
},
"sso_redirect_options": {
"immediate": true,
"on_welcome_page": true,
"on_login_page": true
},
"room_directory": { "servers": ["matrix.org"] },
"sso_redirect_options": { "immediate": true, "on_welcome_page": true, "on_login_page": true },
"setting_defaults": {
"UIFeature.deactivate": false,
"UIFeature.advancedEncryption": false,
@@ -52,7 +37,6 @@
"UIFeature.advancedSettings": false,
"UIFeature.feedback": false,
"custom_themes": [
"HealthChat Light",
{
"name": "HealthChat Light",
"is_dark": false,
@@ -80,11 +64,18 @@
"reaction-row-button-selected-bg-color": "#d9d9d9",
"togglesw-off-color": "#9b9898",
"avatar-background-colors": ["#12325c", "#1f4d85", "#376A91"],
"username-colors": ["#12325c", "#2a4063", "#1f4d85", "#376A91", "#473e3e", "#1f4d85", "#2a4063", "#12325c"]
},
"fonts": {
"general": "\"Century Gothic\", \"Futura\", \"Trebuchet MS\", \"Arial\", sans-serif"
"username-colors": [
"#12325c",
"#2a4063",
"#1f4d85",
"#376A91",
"#473e3e",
"#1f4d85",
"#2a4063",
"#12325c"
]
},
"fonts": { "general": "\"Century Gothic\", \"Futura\", \"Trebuchet MS\", \"Arial\", sans-serif" },
"compound": {
"--cpd-color-text-action-accent": "#1f4d85",
"--cpd-color-bg-action-primary-rest": "#1f4d85",
@@ -99,8 +90,5 @@
"show_labs_settings": false,
"update_base_url": "https://healthchat.ch/packages/desktop/update/",
"modules": ["/modules/healthchat-module.js"],
"ch.healthchat": {
"api_base_url": "https://api.healthchat.ch",
"elements_base_url": "/webapp/modules/elements"
}
"ch.healthchat": { "api_base_url": "https://api.healthchat.ch", "elements_base_url": "/webapp/modules/elements" }
}
+22 -42
View File
@@ -8,44 +8,25 @@
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"default_country_code": "GB",
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.healthchat.ch",
"server_name": "healthchat.ch"
},
"m.identity_server": {
"base_url": ""
}
"m.homeserver": { "base_url": "https://matrix.healthchat.ch", "server_name": "healthchat.ch" },
"m.identity_server": { "base_url": "" }
},
"default_theme": "custom-HealthChat Light",
"disable_custom_urls": true,
"disable_guests": true,
"element_call": {},
"embedded_pages": {
"homeUrl": ""
},
"embedded_pages": { "homeUrl": "" },
"enable_presence_by_hs_url": "",
"features": {},
"integrations_jitsi_widget_url": "https://scalar.vector.im/api/widgets/jitsi.html",
"integrations_rest_url": "https://scalar.vector.im/api",
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_widgets_urls": [
"https://scalar.vector.im/api"
],
"jitsi": {
"preferred_domain": "jitsi.healthchat.ch"
},
"integrations_widgets_urls": ["https://scalar.vector.im/api"],
"jitsi": { "preferred_domain": "jitsi.healthchat.ch" },
"logout_redirect_url": "",
"matrix_to_url": "https://to.healthchat.ch",
"room_directory": {
"servers": [
"matrix.org"
]
},
"sso_redirect_options": {
"immediate": true,
"on_welcome_page": true,
"on_login_page": true
},
"room_directory": { "servers": ["matrix.org"] },
"sso_redirect_options": { "immediate": true, "on_welcome_page": true, "on_login_page": true },
"setting_defaults": {
"UIFeature.deactivate": false,
"UIFeature.advancedEncryption": false,
@@ -55,7 +36,6 @@
"UIFeature.advancedSettings": false,
"UIFeature.feedback": false,
"custom_themes": [
"HealthChat Light",
{
"name": "HealthChat Light",
"is_dark": false,
@@ -74,8 +54,8 @@
"timeline-highlights-color": "#d9d9d9",
"timeline-text-color": "#473e3e",
"timeline-text-secondary-color": "#9b9898",
"secondary-content": "#6e6a6a",
"tertiary-content": "#6e6a6a",
"secondary-content": "#9b9898",
"tertiary-content": "#9b9898",
"focus-bg-color": "#d9d9d9",
"menu-selected-color": "#d9d9d9",
"other-user-pill-bg-color": "#376A91",
@@ -83,30 +63,30 @@
"reaction-row-button-selected-bg-color": "#d9d9d9",
"togglesw-off-color": "#9b9898",
"avatar-background-colors": ["#12325c", "#1f4d85", "#376A91"],
"username-colors": ["#12325c", "#2a4063", "#1f4d85", "#376A91", "#473e3e", "#1f4d85", "#2a4063", "#12325c"]
},
"fonts": {
"general": "\"Century Gothic\", \"Futura\", \"Trebuchet MS\", \"Arial\", sans-serif"
"username-colors": [
"#12325c",
"#2a4063",
"#1f4d85",
"#376A91",
"#473e3e",
"#1f4d85",
"#2a4063",
"#12325c"
]
},
"fonts": { "general": "\"Century Gothic\", \"Futura\", \"Trebuchet MS\", \"Arial\", sans-serif" },
"compound": {
"--cpd-color-text-action-accent": "#1f4d85",
"--cpd-color-bg-action-primary-rest": "#1f4d85",
"--cpd-color-bg-action-primary-hovered": "#2a4063",
"--cpd-color-bg-action-primary-pressed": "#12325c",
"--cpd-color-text-link-external": "#1f4d85",
"--cpd-color-icon-accent-tertiary": "#376A91",
"--cpd-color-icon-tertiary": "#5c6470",
"--cpd-color-icon-quaternary": "#7c8591",
"--cpd-color-icon-accent-primary": "#1f4d85",
"--cpd-color-icon-info-primary": "#1f4d85"
"--cpd-color-icon-accent-tertiary": "#376A91"
}
}
]
},
"show_labs_settings": false,
"modules": ["/modules/healthchat-module.js"],
"ch.healthchat": {
"api_base_url": "http://localhost:5001",
"elements_base_url": "/modules/elements"
}
"ch.healthchat": { "api_base_url": "http://localhost:5001", "elements_base_url": "/modules/elements" }
}
@@ -0,0 +1,41 @@
{
"name": "HealthChat Light",
"is_dark": false,
"colors": {
"accent-color": "#1f4d85",
"primary-color": "#1f4d85",
"alert": "#df8e1d",
"warning-color": "#d20f39",
"sidebar-color": "#12325c",
"roomlist-background-color": "#f5f5f5",
"roomlist-highlights-color": "#d9d9d9",
"roomlist-separator-color": "#9b9898",
"roomlist-text-color": "#473e3e",
"roomlist-text-secondary-color": "#9b9898",
"timeline-background-color": "#ffffff",
"timeline-highlights-color": "#d9d9d9",
"timeline-text-color": "#473e3e",
"timeline-text-secondary-color": "#9b9898",
"secondary-content": "#9b9898",
"tertiary-content": "#9b9898",
"focus-bg-color": "#d9d9d9",
"menu-selected-color": "#d9d9d9",
"other-user-pill-bg-color": "#376A91",
"room-highlight-color": "#376A91",
"reaction-row-button-selected-bg-color": "#d9d9d9",
"togglesw-off-color": "#9b9898",
"avatar-background-colors": ["#12325c", "#1f4d85", "#376A91"],
"username-colors": ["#12325c", "#2a4063", "#1f4d85", "#376A91", "#473e3e", "#1f4d85", "#2a4063", "#12325c"]
},
"fonts": {
"general": "\"Century Gothic\", \"Futura\", \"Trebuchet MS\", \"Arial\", sans-serif"
},
"compound": {
"--cpd-color-text-action-accent": "#1f4d85",
"--cpd-color-bg-action-primary-rest": "#1f4d85",
"--cpd-color-bg-action-primary-hovered": "#2a4063",
"--cpd-color-bg-action-primary-pressed": "#12325c",
"--cpd-color-text-link-external": "#1f4d85",
"--cpd-color-icon-accent-tertiary": "#376A91"
}
}
+25
View File
@@ -0,0 +1,25 @@
HealthChat theme — single source of truth
=========================================
HealthChat-Light-Theme.json The canonical "HealthChat Light" theme.
Edit the palette HERE and nowhere else. apps/web/scripts/healthchat-sync-theme.mjs injects
it into the element-web configs that ship it:
- apps/web/config.json (dev server; also baked into the web image)
- apps/desktop/element.io/healthchat/config.json (desktop variant, bundled into the app)
After editing, run:
pnpm --filter element-web healthchat:theme
`pnpm lint` runs `healthchat:theme:check`, which fails if a config is out of sync — so an
un-propagated edit is caught in CI.
Production web deployment
-------------------------
The production web client is deployed by matrix-docker-ansible-deploy, whose
matrix-client-element role renders config.json from host_vars (no upstream role change).
It carries an INLINE copy of this theme in
inventory/host_vars/matrix.healthchat.ch/vars.yml
(matrix_client_element_setting_defaults_custom_themes)
When you change the theme here, re-copy it into that host_vars block. themes_enabled is off
there, so the (stale) Novaloop-AG/element-themes repo does not append an older theme.
+3 -1
View File
@@ -18,13 +18,15 @@
"i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null",
"i18n:diff": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && pnpm i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
"rethemendex": "sh ./res/css/rethemendex.sh",
"healthchat:theme": "node scripts/healthchat-sync-theme.mjs",
"healthchat:theme:check": "node scripts/healthchat-sync-theme.mjs --check",
"build": "nx build",
"build-stats": "nx build --json=webpack-stats.json",
"vendor:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js && mv src/vector/modernizr.js src/vector/modernizr.cjs",
"vendor:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js",
"dist": "./scripts/package.sh",
"start": "nx start",
"lint": "pnpm lint:types && pnpm lint:js && pnpm lint:style",
"lint": "pnpm healthchat:theme:check && pnpm lint:types && pnpm lint:js && pnpm lint:style",
"lint:js": "eslint --max-warnings 0 src test playwright module_system",
"lint:js-fix": "eslint --fix src test playwright module_system",
"lint:types": "nx lint:types",
@@ -0,0 +1,70 @@
#!/usr/bin/env node
/*
* HealthChat theme sync.
*
* Single source of truth for the "HealthChat Light" theme lives in
* apps/web/element.io/healthchat/HealthChat-Light-Theme.json
* This script injects it into every element-web config that ships the theme, so the palette
* is authored once and never drifts between the dev and desktop configs.
*
* The production web deployment (matrix-docker-ansible-deploy) renders its config.json from
* host_vars and carries an inline copy of this theme; re-copy this file there when it changes.
*
* Usage:
* node apps/web/scripts/healthchat-sync-theme.mjs # write the theme into all configs
* node apps/web/scripts/healthchat-sync-theme.mjs --check # exit 1 if any config is out of sync
*
* Run `pnpm --filter element-web healthchat:theme` after editing the canonical theme;
* the --check variant runs in lint/CI to catch an un-synced edit.
*/
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import prettier from "prettier";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(scriptDir, "../../..");
const CANONICAL = resolve(repoRoot, "apps/web/element.io/healthchat/HealthChat-Light-Theme.json");
// Configs that must carry the theme in setting_defaults.custom_themes.
const TARGETS = [
"apps/web/config.json", // dev server config (also served by the web Docker image)
"apps/desktop/element.io/healthchat/config.json", // desktop variant config bundled into the app
];
const check = process.argv.includes("--check");
const theme = JSON.parse(readFileSync(CANONICAL, "utf8"));
let changed = 0;
for (const rel of TARGETS) {
const path = resolve(repoRoot, rel);
const raw = readFileSync(path, "utf8");
const config = JSON.parse(raw);
config.setting_defaults ??= {};
config.setting_defaults.custom_themes = [theme];
// Format identically to `prettier --write` so the output is byte-for-byte what the
// repo's formatting lint expects, keeping --check stable.
const options = await prettier.resolveConfig(path);
const next = await prettier.format(JSON.stringify(config), { ...options, filepath: path });
if (next === raw) {
continue;
}
changed++;
if (check) {
console.error(`out of sync: ${rel} (run: pnpm --filter element-web healthchat:theme)`);
} else {
writeFileSync(path, next);
console.log(`synced theme -> ${rel}`);
}
}
if (check) {
if (changed > 0) process.exit(1);
console.log("all configs in sync with the canonical HealthChat theme");
} else if (changed === 0) {
console.log("all configs already in sync");
}