Compare commits

...

37 Commits

Author SHA1 Message Date
joel.poyet 66bfe9250e chore(release): HealthChat 1.7.2 2026-10-25 09:49:42 +01:00
joel.poyet b1e36ff959 fix(mobile-guide): German HealthChat guide for phones, proven in Chromium
Phones and tablets were bounced to the healthchat.ch website, where the
visitor had no way back into the app. They now land on the mobile guide
again, which pointed at Element X and spoke English while the rest of
HealthChat web is German.

The guide carries the HealthChat store links and German copy as static
HTML, not only via JS: Safari reads the Smart App Banner meta tag at
parse time. A charset meta is required now that the page has umlauts,
since nginx sends text/html without one. Step 2 and the F-Droid line
stay hidden because HealthChat has neither a mobile.element.io deep link
nor an F-Droid build.

Verified in Chromium at phone width with iPhone and Android user agents:
redirect, store links, the way back via the cookie, and the no-JS view.
2026-10-25 09:49:42 +01:00
joel.poyet dcfd82b773 fix(app-links): claim app.healthchat.ch for HealthChat only, proven in build
The AASA and assetlinks files still carried the upstream Element and
Element X app IDs. A phone with one of those apps installed opened
HealthChat links there, while the HealthChat iOS app was not listed at
all, so its Universal Links could never resolve. Debug and nightly
entries are gone because we never publish such builds.

Both files validate as JSON and are served from the webapp build at the
paths iOS and Android fetch.
2026-10-25 09:49:42 +01:00
Sedat Qaja c66c7797e4 chore(release): HealthChat 1.7.1 2026-10-21 18:53:12 +02:00
Sedat Qaja 04ccaf3e78 fix(calls): ship LiveKit fallback in Element Call config
Well-known fetch fails CORS on IPv4 (Wix redirect), so the embedded
widget needs the JWT service URL in its own config.json.
2026-10-21 18:37:21 +02:00
Sedat Qaja 11e3f90c81 docs: update image path after repository rename 2026-09-28 13:13:05 +02:00
Sedat Qaja 45bbbd7379 chore(desktop): bump electron-builder to 26.16.1 2026-09-28 10:38:50 +02:00
Sedat Qaja 8f15f0352f chore(release): HealthChat 1.7.0 2026-09-28 09:37:14 +02:00
Sedat Qaja 6f8df665ff feat(calls): enable Element Call in desktop config 2026-09-28 09:37:14 +02:00
joel.poyet 697c7efe15 ci: label desktop download links with platform 2026-07-29 23:32:28 +02:00
joel.poyet 637eaa7356 ci: retire legacy healthchat.ch desktop feed 2026-07-29 23:31:39 +02:00
joel.poyet 30bb2bfca3 ci: stage desktop feed on packages.healthchat.ch archive 2026-07-29 23:28:39 +02:00
joel.poyet 09e66dba47 ci: link desktop installers instead of attaching them 2026-07-29 23:19:35 +02:00
joel.poyet 8530b1f09a ci: split desktop feed tarball under 2 GiB asset cap 2026-07-29 23:08:03 +02:00
joel.poyet 10a380bc81 chore(release): HealthChat 1.6.0 2026-07-29 22:32:04 +02:00
joel.poyet ab295d810c chore: move desktop packages to packages.healthchat.ch 2026-07-29 22:28:43 +02:00
joel.poyet 675e812f00 docs: document manual deploy for web and desktop releases 2026-07-29 00:39:16 +02:00
joel.poyet 831a928d04 ci: split desktop feed publish into a manual deploy workflow 2026-07-29 00:39:16 +02:00
joel.poyet f345ca55b3 ci: publish :latest instead of :stable for stable tags 2026-07-29 00:39:16 +02:00
joel.poyet ab91e1a7bc chore(release): HealthChat 1.5.0 2026-07-28 23:41:33 +02:00
joel.poyet be3ab5fb3d chore(release): HealthChat 1.4.1 2026-07-14 14:02:53 +02:00
joel.poyet f33708c705 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.
2026-07-14 14:02:39 +02:00
joel.poyet ce7f9230c3 chore(release): HealthChat 1.4.0 2026-07-14 09:13:58 +02:00
joel.poyet 5e3f235442 chore(release): HealthChat 1.3.1 2026-07-10 18:01:11 +02:00
joel.poyet 0d2ccb9d59 feat(settings): add sign out button to Help & About tab 2026-07-10 17:54:30 +02:00
joel.poyet a9cd6fabb4 chore(release): HealthChat 1.3.0 2026-07-08 23:13:59 +02:00
joel.poyet 745069128f style(spaces): use compound tokens for hierarchy colors 2026-07-08 19:59:50 +02:00
joel.poyet 1697be5239 feat(settings): re-enable slim Help & About tab 2026-07-08 18:25:50 +02:00
joel.poyet 209cd37d0f chore(release): HealthChat 1.2.5 2026-07-01 08:42:57 +02:00
joel.poyet d2aa9ef7a0 chore(release): HealthChat 1.2.4 2026-07-01 08:05:32 +02:00
joel.poyet 8a2754c7c0 fix(desktop): brand Windows installer splash as HealthChat 2026-07-01 08:03:31 +02:00
joel.poyet ead9847d76 chore(web): point help_url at healthchat.ch 2026-07-01 07:20:31 +02:00
joel.poyet 9d53c2b131 chore(desktop): brand About panel, author, homepage, and description as HealthChat
- author.name → HealthChat so the macOS About panel copyright reads
  "Copyright © 2026 HealthChat" (electron-builder derives copyright from author.name)
- author.email → support@healthchat.ch
- homepage → https://www.healthchat.ch
- app description (de-CH) → "Die sichere Chat-Lösung für Schweizer HealthProfessionals"
2026-07-01 07:17:53 +02:00
joel.poyet 22b2d8ca6f chore(release): HealthChat 1.2.3 2026-06-30 18:05:40 +02:00
joel.poyet dd461ddca9 fix(desktop): make module location renderers interactive on macOS
The macOS frameless-titlebar CSS marks .mx_RoomView_wrapper as a
-webkit-app-region drag handle, which swallows all mouse/keyboard input
for its descendants. Module location renderers (the HealthChat Angular
custom elements like hc-user-browser / hc-admin-dashboard) are mounted
there but match none of the no-drag exclusions, so their inputs and
buttons were dead in the desktop app while working in the web app.

Exclude the room-view wrapper's direct children from the drag region so
the mounted page receives pointer events; the existing .mx_RoomView child
and the ::before drag strips are unaffected.
2026-06-30 18:03:47 +02:00
joel.poyet 8b4b63b245 fix(desktop): show HealthChat brand in native About panel and menu
The native About panel and macOS app menu derived their name from
Electron's app.name and ignored the configured brand, so they still
showed "Element". Set the About panel options from vectorConfig.brand
and use the brand for the macOS app/About menu labels.
2026-06-30 17:46:12 +02:00
joel.poyet c2b11111dd feat(permalinks): point Share links/QR at to.healthchat.ch
Add a config-driven matrix_to_url option so Share dialog links and QR
codes resolve against a matrix.to-compatible resolver instead of the
hardcoded public matrix.to. MatrixToPermalinkConstructor now reads the
base URL lazily from config (default https://matrix.to), keeping the
matrix.to URL format. HealthChat configs point it at the in-house fork
https://to.healthchat.ch and drop the conflicting permalink_prefix
(which routes to the Element-format constructor a matrix.to fork can't
parse).
2026-06-30 17:30:56 +02:00
42 changed files with 1180 additions and 834 deletions
+225 -31
View File
@@ -9,24 +9,57 @@ Releases are driven by git tags of the form `healthchat-v<semver>` pushed to
`origin`. The tag triggers two workflows in parallel:
- `.github/workflows/build-healthchat.yaml` (web) — builds the HealthChat Docker
image, publishes it to ghcr.io under `healthchat-web:<version>`, `:stable`, and
image, publishes it to ghcr.io under `healthchat-web:<version>`, `:latest`, and
`:<sha>`, and creates a GitHub Release whose body is the matching section of
`HEALTHCHAT_CHANGES.md`.
**`:latest` is the tag production deploys from.** It only moves for a clean
`X.Y.Z` **tag push** — never for `-rc1`/`-hotfix1`/`-dryrun1` tags, and never for
a manual `workflow_dispatch` run. Those builds still publish `:<version>` and
`:<sha>`, so you can deploy them deliberately by overriding the image ref (see
"Deploying the release"), but they can never become production by accident.
- `.github/workflows/build-healthchat-desktop.yaml` (desktop) — builds the Electron
app for macOS (universal, signed+notarised) and Windows (x64/arm64, signed), and
attaches the installers (.dmg/.zip/.exe/.msi) to the **same** GitHub Release. The two
workflows create-if-missing then upload, so they race safely. (Linux is intentionally
not built — the upstream Linux reusable pulls a build-env Docker image keyed by commit
SHA that only exists in the upstream element-hq registry, so it cannot succeed in this
fork.) The `release` job additionally **mirrors the Squirrel update feed and the public
installers to `https://healthchat.ch/packages/desktop/` over FTPS** (the private GitHub
Release can serve neither end users nor the auto-updater). This is what makes installed
apps self-update; see `apps/desktop/element.io/healthchat/README` for the feed layout and
app for macOS (universal, signed+notarised) and Windows (x64/arm64, signed) and
publishes to the **same** GitHub Release as the web workflow (both create-if-missing,
so they race safely). (Linux is intentionally not built — the upstream Linux reusable
pulls a build-env Docker image keyed by commit SHA that only exists in the upstream
element-hq registry, so it cannot succeed in this fork.) The `release` job **stages**
the Squirrel update feed and public installers and uploads them to the versioned
archive at `https://packages.healthchat.ch/desktop-releases/<version>/` — but it
**does not publish them** to the live feed. Nothing binary is attached to the GitHub
Release (the compressed feed outgrew GitHub's 2 GiB per-asset cap at 1.6.0); the
Release notes get a "## Downloads" section linking the installers' public URLs on
packages.healthchat.ch, which resolve once the version is deployed. See "Deploying
the desktop release" below.
- `.github/workflows/deploy-healthchat-desktop.yaml` (desktop deploy) — **manual
dispatch only.** Fetches the staged archive for the requested version and mirrors it
to the live feed at `https://packages.healthchat.ch/desktop/` over FTPS (the private
GitHub Release can serve neither end users nor the auto-updater). The pre-1.6.0 feed
at `https://healthchat.ch/packages/desktop/` is retired and frozen at 1.5.0 — installs
that old must reinstall once from the new download URL. This is what makes installed apps
self-update; see `apps/desktop/element.io/healthchat/README` for the feed layout and
the two caveats (first install is manual; Windows auto-update needs the Setup.exe path,
not the .msi).
Publish target: **ghcr.io Docker image + GitHub Release (web bundle + desktop
installers).** No npm, no mobile fan-out.
Publish target: **ghcr.io Docker image + versioned feed archive on
packages.healthchat.ch + GitHub Release (web bundle + installer download links).**
No npm, no mobile fan-out.
## Release ≠ deploy
Cutting a tag never changes what any user is running. Both halves of the deployment are
separate, manual, and need no code change:
| Component | Released by | Deployed by |
| --- | --- | --- |
| Web (ghcr.io image) | `healthchat-v*` tag push → `:latest` moves | `gh workflow run deploy.yml` on the ansible repo |
| Desktop (update feed) | `healthchat-v*` tag push → staged feed archive on packages.healthchat.ch | `gh workflow run deploy-healthchat-desktop.yaml` |
Writing the desktop feed pushes an update to **every installed client**, so it is a
deployment, not a publication step — hence the same manual gate as the server side.
Deploy the two together unless you have a reason not to: a desktop client talks to the
web deployment, so leaving one behind means users run a client built against a different
release.
The desktop app version is taken from `apps/desktop/package.json` (the per-job
`version` input cannot set it — Windows squirrel mangles it into a nightly string,
@@ -99,6 +132,123 @@ Examples:
The workflow triggers automatically. Watch it via `gh run watch` or GitHub
Actions UI.
## Deploying the release
Publishing the image does **not** deploy it. Deployment is a separate manual
dispatch against the ansible repo — but it needs **no code change anywhere**: the
inventory pins `matrix_client_element_container_image` to
`ghcr.io/novaloop-ag/healthchat-web/healthchat-web:latest`, and the ansible role pulls
with `pull: always`, so it picks up whatever `:latest` now points at and restarts
the container only if the digest actually moved.
The image path is derived from the repository name
(`format('{0}/healthchat-web', github.repository)`). Renaming the repository moves
the image to a new package and the old one stops receiving builds, so the inventory
pin must follow. This happened on 2026-07-29 when `element-web` became
`healthchat-web`; 1.7.0 was the first release that hit it.
Wait for the release workflow to go green first (the deploy pulls whatever is in the
registry at that moment), then:
```bash
gh workflow run deploy.yml \
--repo Novaloop-AG/matrix-docker-ansible-deploy \
--ref release/healthchat \
-f command="install-service client-element"
```
Watch it: `gh run watch --repo Novaloop-AG/matrix-docker-ansible-deploy`.
Or via the UI: GitHub → `Novaloop-AG/matrix-docker-ansible-deploy` → Actions →
"Deploy HealthChat (Synapse, Web app etc.)" → Run workflow → **command:**
`install-service client-element`.
Add `-f check_mode=true` for a dry run (`--check --diff`).
### Deploying a specific version instead of `:latest`
RCs, hotfix tags and dispatch builds do not move `:latest`. To ship one — or to roll
back — pass an explicit image ref through `extra_args`, which the workflow forwards
verbatim to `ansible-playbook`. **No inventory edit, no commit:**
```bash
gh workflow run deploy.yml \
--repo Novaloop-AG/matrix-docker-ansible-deploy \
--ref release/healthchat \
-f command="install-service client-element" \
-f extra_args="-e matrix_client_element_container_image=ghcr.io/novaloop-ag/healthchat-web/healthchat-web:1.5.0"
```
Note this is a one-shot override: the *next* plain deploy returns to `:latest`. For a
sustained hold, pin the version in the inventory instead.
### Confirming what actually shipped
Because the inventory tracks `:latest`, git no longer records the deployed version —
check the running app, not the repo:
```bash
curl -fsS https://app.healthchat.ch/version
```
That serves the build version baked in by `scripts/docker-package.sh` (e.g.
`healthchat-v1.5.0`). If it still reports the old version, the deploy either did not
run or `:latest` had not moved yet.
## Deploying the desktop release
The desktop build stages the feed at
`https://packages.healthchat.ch/desktop-releases/<version>/` but
leaves the live feed at `https://packages.healthchat.ch/desktop/` untouched. Until you run this, installed
apps keep whatever version they have and the public download links still serve the old
installers.
Wait for "Build HealthChat Desktop" to go green, then:
```bash
gh workflow run deploy-healthchat-desktop.yaml \
--ref release/healthchat \
-f version=1.5.0
```
Or: GitHub → Actions → "Deploy HealthChat Desktop" → Run workflow → **version:** `1.5.0`.
Dry run first if you want to see what would be written without touching the server:
```bash
gh workflow run deploy-healthchat-desktop.yaml \
--ref release/healthchat \
-f version=1.5.0 -f dry_run=true
```
The workflow refuses to publish a partial feed — it fails before contacting the server if
`releases.json`, either arch's `RELEASES`, or the macOS `*-mac.zip` payload is missing. A
half-written feed breaks auto-update for everyone already installed, which is much worse
than not deploying.
On success it curls the five load-bearing URLs and fails if any is not 200, so a green run
means the feed is genuinely live. Installed apps pick it up on their next update check.
### Deploying an RC or rolling back
`version` accepts any released version, including `1.5.0-rc1`. Because staged feeds
persist in the versioned archive (`desktop-releases/<version>/`), **re-running this
workflow with an older version republishes that feed** — no rebuild, no re-signing.
That is the desktop rollback path:
```bash
gh workflow run deploy-healthchat-desktop.yaml --ref release/healthchat -f version=1.4.1
```
Caveat: `mirror -R` does not delete, so rolling back rewrites the manifests but leaves the
newer `*.nupkg` files in place. That is intentional — Squirrel.Windows needs the delta
history — and harmless, since clients follow `RELEASES`/`releases.json`, not directory
listings.
Versions with no staged archive (cut before the archive design) fail the deploy with a
pointer; dispatch "Build HealthChat Desktop" with `version=<version>` to rebuild and
stage that version, then re-run the deploy.
## Manual / retry path (workflow_dispatch)
Use when retrying a failed run, testing the pipeline, or rebuilding an image
@@ -112,12 +262,16 @@ without moving tags.
lookup.
3. Run. Docker image is pushed. **No GitHub Release is created on the dispatch
path** — only the tag-push path creates Releases (keeps manual retries from
clobbering the release history).
clobbering the release history). **`:latest` is not moved either**, for the same
reason: a retry or pipeline test must not become production. The build still
publishes `:<version>` and `:<sha>`; deploy it with the explicit-version
override in "Deploying the release" if that is what you want.
The desktop workflow ("Build HealthChat Desktop") has its own workflow_dispatch
with the same inputs; like the web path, its `release` job only publishes on a
real tag push, so a manual dispatch builds and uploads run artifacts but does not
attach anything to a Release.
with the same inputs. Its `release` job **also runs on dispatch** to (re)stage the
feed archive at `desktop-releases/<version>/` — that is the recovery path when a
version's archive is missing — but the GitHub Release steps (create + Downloads
links) stay tag-only, so a dispatch can never touch release history.
## Tag format
@@ -161,22 +315,44 @@ warning. Prefer fixing the changelog over relying on the fallback.
## After the release
- [ ] Workflow run is green in GitHub Actions.
- [ ] `docker pull ghcr.io/<owner>/healthchat-web:<version>` succeeds.
- [ ] `docker pull ghcr.io/novaloop-ag/healthchat-web/healthchat-web:<version>` succeeds.
(Note the image path contains the repo segment — the workflow builds it as
`format('{0}/healthchat-web', github.repository)`.)
- [ ] GitHub Release exists at
`https://github.com/<owner>/<repo>/releases/tag/healthchat-v<version>`
with the correct body.
- [ ] The `:stable` tag on ghcr.io now points at the new digest
(`docker buildx imagetools inspect ghcr.io/<owner>/healthchat-web:stable`).
- [ ] The desktop workflow run ("Build HealthChat Desktop") is green and the
GitHub Release has the desktop installers attached (.dmg, *-mac.zip, *.msi,
"* Setup*.exe").
- [ ] The update feed + installers published to healthchat.ch: `curl -fsI`
`https://healthchat.ch/packages/desktop/update/macos/releases.json`,
`.../update/win32/x64/RELEASES`, and the public installer URLs
`.../install/macos/HealthChat.dmg` / `.../install/win32/x64/HealthChat-Setup.exe`
all return 200.
- [ ] Smoke-check signing: macOS `spctl -a -vvv -t install <app>` is accepted;
Windows installer shows a valid publisher (no SmartScreen block).
- [ ] The `:latest` tag on ghcr.io now points at the new digest, and matches
`:<version>`:
```bash
docker buildx imagetools inspect ghcr.io/novaloop-ag/healthchat-web/healthchat-web:latest
docker buildx imagetools inspect ghcr.io/novaloop-ag/healthchat-web/healthchat-web:<version>
```
For an `-rc`/`-hotfix`/`-dryrun` tag the opposite is expected: `:latest` must
be **unchanged**.
- [ ] The desktop workflow run ("Build HealthChat Desktop") is green, the GitHub
Release notes carry the "## Downloads" section, and the staged archive exists:
`curl -fsS https://packages.healthchat.ch/desktop-releases/<version>/update/macos/releases.json`.
Without that archive the desktop deploy has nothing to publish.
- [ ] Confirm the live feed is still on the **previous** version — releasing must not have
moved it. `curl -fsS https://packages.healthchat.ch/desktop/update/macos/releases.json`
- [ ] Smoke-check signing before shipping to users: macOS `spctl -a -vvv -t install <app>`
is accepted; Windows installer shows a valid publisher (no SmartScreen block).
## After the deployment
Two independent deploys — do both (see "Deploying the release" and "Deploying the desktop
release"):
- [ ] Web deployed and `curl -fsS https://app.healthchat.ch/version` reports the new version.
- [ ] Desktop feed deployed: the "Deploy HealthChat Desktop" run is green. Its own verify
step already asserts the feed URLs return 200, so a green run is
the check.
- [ ] `releases.json` now advertises the new version:
`curl -fsS https://packages.healthchat.ch/desktop/update/macos/releases.json | jq -r '.releases[0].updateTo.version'`
- [ ] Spot-check auto-update on a real installed client (or at least confirm the app's
update check reaches the feed) before announcing.
## Required secrets
@@ -186,7 +362,7 @@ Configured in repo Settings → Secrets and variables → Actions:
|---|---|
| `HEALTHCHAT_WORKSPACE_TOKEN` | PAT with read access to `Novaloop-AG/healthchat-workspace`. Needed by `actions/checkout` for the private plugin repo. |
| `GITHUB_TOKEN` | Auto-provided. Used for ghcr.io login and GitHub Release creation. No manual setup required. |
| `FTP_SERVER` / `FTP_USERNAME` / `FTP_PASSWORD` | Credentials for the healthchat.ch FTP(S) account. The desktop `release` job uses them to publish the update feed + public installers under `/public_html/healthchat.ch/packages/desktop/`. `FTP_SERVER` is the hostname only (explicit FTPS / AUTH TLS). Without them the FTP publish step fails (but the GitHub Release still gets the installers). |
| `FTP_SERVER` / `FTP_USERNAME` / `FTP_PASSWORD` | Credentials for the healthchat.ch FTP(S) account (docroot of packages.healthchat.ch is `/public_html/packages`). Used by the `release` job of `build-healthchat-desktop.yaml` to stage the feed archive under `/public_html/packages/desktop-releases/<version>/`, and by `deploy-healthchat-desktop.yaml` to publish the live feed under `/public_html/packages/desktop/`. `FTP_SERVER` is the hostname only (explicit FTPS / AUTH TLS). |
Desktop signing additionally requires two GitHub **environments** (Settings →
Environments). Without them the desktop builds still run but produce
@@ -206,9 +382,26 @@ fork must stay **internal** (not public). Add branch/environment protection so o
## Rollback
- **Bad image already deployed** → redeploy the previous version immediately using the
explicit-version override in "Deploying the release" (no commit, no inventory edit).
That restores service; then fix forward with a hotfix tag. Remember the override is
one-shot — the next plain deploy goes back to `:latest`, so land the real fix before
anyone runs `setup-all`.
- **Bad image, notes fine** → fix the cause, commit, tag `healthchat-v<ver>-hotfix1`.
Note a `-hotfix1` tag does **not** move `:latest`; once you have verified it, either
deploy it via the explicit-version override or cut a clean `X.Y.Z` tag to promote it.
- **Bad desktop build already deployed to the feed** → re-run "Deploy HealthChat Desktop"
with the previous version (see "Deploying an RC or rolling back"). Clients that already
auto-updated stay on the bad version until the next release — the feed only controls what
is offered next, it cannot recall an installed build. That asymmetry is exactly why the
desktop deploy is gated behind a manual dispatch.
- **Bad desktop build caught before deploying** → nothing to undo. Do not run the deploy
workflow; fix forward and cut a new tag.
- **Bad release notes** → edit `HEALTHCHAT_CHANGES.md` and the GitHub Release
body manually (GitHub UI). The ghcr.io tag is immutable, don't try to move it.
body manually (GitHub UI). The ghcr.io tag is immutable, don't try to move it. Note the
desktop feed bakes the notes in at **build** time (into `releases.json` and `notes.txt`),
so a post-hoc edit to `HEALTHCHAT_CHANGES.md` does not reach the update dialog — rebuild
that version if the notes matter to desktop users.
- **Completely wrong tag** → delete the GitHub Release (UI). Do **not** delete
the ghcr.io image tag — treat it as burned. Re-tag with a new version.
@@ -220,6 +413,7 @@ during rebase):
- `.github/workflows/build-healthchat.yaml`
- `.github/workflows/build-healthchat-desktop.yaml`
- `.github/workflows/deploy-healthchat-desktop.yaml`
- `HEALTHCHAT_CHANGES.md`
- `.healthchat-workspace-version`
- `.claude/skills/releasing-healthchat/SKILL.md`
+105 -39
View File
@@ -10,6 +10,12 @@ name: Build HealthChat Desktop
# (via `webapp-artifact`) and the per-platform build_desktop_{macos,windows}.yaml reusables.
#
# Notes:
# - This workflow RELEASES but does not DEPLOY. It stages the update feed + public installers
# to the versioned archive at packages.healthchat.ch/desktop-releases/<version>/ and links
# the public installer URLs in the Release notes (nothing binary is attached to the GitHub
# Release); the live auto-update feed at packages.healthchat.ch/desktop/ is only touched by
# the manually dispatched deploy-healthchat-desktop.yaml. So cutting a tag never pushes an
# update to already-installed apps until a human decides to ship it.
# - The reusable build workflows are pointed at THIS fork (repository/ref inputs) so the Electron
# shell, icons (apps/desktop/build/icon.*) and the healthchat:// deep-link handler
# (apps/desktop/src/protocol.ts) are all fork-branded. Branding values come from
@@ -191,10 +197,10 @@ jobs:
targets: "dmg zip"
# Generates dist/releases.json (the Squirrel.Mac feed manifest). The reusable appends
# `/update/macos/<zip>` to this, so the in-manifest URL becomes
# https://healthchat.ch/packages/desktop/update/macos/<zip>, matching where the
# https://packages.healthchat.ch/desktop/update/macos/<zip>, matching where the
# `release` job publishes the feed. Keep in sync with update_base_url in
# element.io/healthchat/config.json (.../packages/desktop/update/).
base-url: https://healthchat.ch/packages/desktop
# element.io/healthchat/config.json (.../desktop/update/).
base-url: https://packages.healthchat.ch/desktop
windows:
name: Windows ${{ matrix.arch }}
@@ -223,10 +229,13 @@ jobs:
targets: "squirrel msi"
release:
name: Publish to GitHub Release
name: Stage feed archive + Release notes
needs: [macos, windows]
# Only publish on a real tag push; manual workflow_dispatch runs build but do not publish.
if: startsWith(github.ref, 'refs/tags/')
# Runs on tag pushes AND manual dispatches: the archive upload is idempotent per version
# (mirror --delete into desktop-releases/<version>/), and re-running it via dispatch is
# the recovery path for a version whose archive is missing. The GitHub Release steps
# below stay tag-only, so a dispatch can never touch release history.
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
@@ -237,24 +246,31 @@ jobs:
with:
persist-credentials: false
- name: Resolve version
id: relversion
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
set -euo pipefail
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
VERSION="${GITHUB_REF_NAME#healthchat-v}"
else
VERSION="${INPUT_VERSION:?No version input on workflow_dispatch}"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Download all build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: dist-artifacts
- name: Collect installers
run: |
set -euo pipefail
mkdir -p release-files
# macOS: .dmg installer + .zip (Squirrel.Mac update payload)
find dist-artifacts -type f \( -iname '*.dmg' -o -iname '*-mac.zip' \) -exec cp -v {} release-files/ \;
# Windows: per-machine MSI + Squirrel "Setup" installer
find dist-artifacts -type f \( -iname '*.msi' -o -iname '* Setup*.exe' \) -exec cp -v {} release-files/ \;
echo "--- release files ---"
ls -la release-files
test -n "$(ls -A release-files)" || { echo "::error::No installer files collected"; exit 1; }
- name: Upload to GitHub Release
# Installers are NOT attached to the Release: they ship inside the staged feed and are
# served from packages.healthchat.ch after the deploy — the Release body links to them
# there (see "Link public downloads in the Release notes" below). Attaching them here
# would duplicate ~1 GiB per release and the x64/arm64 Setup.exe share a filename, so
# they'd clobber each other as flat Release assets anyway.
- name: Ensure GitHub Release exists
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
@@ -262,17 +278,21 @@ jobs:
run: |
set -euo pipefail
# The web workflow (build-healthchat.yaml) also creates this release for the same tag.
# Create-if-missing (without touching existing notes) then upload --clobber so the two
# workflows race safely regardless of ordering.
# Create-if-missing (without touching existing notes) so the two workflows race
# safely regardless of ordering.
gh release view "$TAG" >/dev/null 2>&1 || \
gh release create "$TAG" --title "$TAG" --notes "HealthChat desktop installers for $TAG"
gh release upload "$TAG" release-files/* --clobber
gh release create "$TAG" --title "$TAG" --notes "HealthChat $TAG"
# The GitHub Release above is private (internal fork), so it can't serve end users or the
# Squirrel auto-updater. Mirror the update feed + public installers to healthchat.ch over
# FTPS so installed apps self-update (update_base_url -> .../packages/desktop/update/) and
# users can download from a public URL (.../packages/desktop/install/).
# The GitHub Release is private (internal fork), so it can't serve end users or the
# Squirrel auto-updater. Those are served from packages.healthchat.ch/desktop/, which is
# published by the SEPARATE, MANUALLY DISPATCHED "Deploy HealthChat Desktop" workflow
# (deploy-healthchat-desktop.yaml) — releasing never touches the live update feed.
# This job only *stages* that feed and uploads it to the versioned archive at
# packages.healthchat.ch/desktop-releases/<version>/ (the archive persists, so the
# deploy workflow can publish any staged version without rebuilding or re-signing).
- name: Stage update feed + public installers
env:
VERSION: ${{ steps.relversion.outputs.version }}
run: |
set -euo pipefail
# --- Release notes: extract this version's section from HEALTHCHAT_CHANGES.md ---
@@ -280,7 +300,6 @@ jobs:
# auto-update "See changes" dialog: macOS reads it from releases.json's `notes`
# field natively, Windows fetches notes.txt (Squirrel.Windows can't carry notes
# through the update-downloaded event — only releaseName is available).
VERSION="${GITHUB_REF_NAME#healthchat-v}"
NOTES=$(awk -v ver="$VERSION" '
/^## Changes in / {
if (found) { exit }
@@ -328,31 +347,78 @@ jobs:
echo "--- staged feed tree ---"
find feed -type f | sort
- name: Install lftp
run: sudo apt-get update && sudo apt-get install -y lftp
- name: Publish to healthchat.ch over FTPS
# Upload the staged feed to the versioned archive instead of publishing it. "Deploy
# HealthChat Desktop" (deploy-healthchat-desktop.yaml) mirrors this archive to the
# live feed when a human decides to ship. The archive directories persist on the
# server, so any staged version stays redeployable without rebuilding or re-signing.
# (The feed used to be attached to the GitHub Release as a tarball, but the
# compressed feed outgrew GitHub's 2 GiB per-asset cap at 1.6.0.)
- name: Publish staged feed to the versioned archive
env:
FTP_SERVER: ${{ secrets.FTP_SERVER }}
FTP_USERNAME: ${{ secrets.FTP_USERNAME }}
# Passed to lftp via LFTP_PASSWORD (--env-password) so it never appears on the
# command line / process list.
LFTP_PASSWORD: ${{ secrets.FTP_PASSWORD }}
VERSION: ${{ steps.relversion.outputs.version }}
run: |
set -euo pipefail
: "${FTP_SERVER:?FTP_SERVER secret not set}"
: "${FTP_USERNAME:?FTP_USERNAME secret not set}"
: "${LFTP_PASSWORD:?FTP_PASSWORD secret not set}"
# mirror -R (reverse) uploads the local tree; default is no-delete, so prior
# *.nupkg are preserved (Squirrel.Windows delta history) and only the staged files
# are written/overwritten. If the host's FTPS cert is untrusted, set
# ssl:verify-certificate to no.
lftp -u "$FTP_USERNAME" --env-password "$FTP_SERVER" <<'LFTP'
sudo apt-get update && sudo apt-get install -y lftp
# The archive dir is owned by this version's build: --delete makes re-runs
# idempotent (a rebuild fully replaces the staged tree). The live feed under
# /public_html/packages/desktop is never touched here.
lftp -u "$FTP_USERNAME" --env-password "$FTP_SERVER" <<LFTP
set cmd:fail-exit true
set ftp:ssl-force true
set ftp:ssl-protect-data true
set ssl:verify-certificate true
mkdir -pf /public_html/healthchat.ch/packages/desktop
mirror -R --parallel=4 --verbose feed/ /public_html/healthchat.ch/packages/desktop/
mkdir -pf /public_html/packages/desktop-releases/$VERSION
mirror -R --delete --parallel=4 --verbose feed/ /public_html/packages/desktop-releases/$VERSION/
bye
LFTP
echo "::notice::Feed staged at https://packages.healthchat.ch/desktop-releases/$VERSION/ — the live feed is UNCHANGED. Run 'Deploy HealthChat Desktop' to publish it."
# The Release carries no installer binaries (see "Ensure GitHub Release exists"), so
# point readers at the public download URLs instead. The links are derived from the
# staged feed/install tree, so they stay correct if electron-builder ever renames the
# artifacts. They resolve once "Deploy HealthChat Desktop" has published this version.
- name: Link public downloads in the Release notes
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
BASE="https://packages.healthchat.ch/desktop"
BODY=$(gh release view "$TAG" --json body --jq .body)
if [[ "$BODY" == *"## Downloads"* ]]; then
echo "Release notes already carry a Downloads section — leaving them untouched."
exit 0
fi
{
printf '%s\n\n' "$BODY"
echo "## Downloads"
echo ""
echo "Public installers, live once this version is deployed via \"Deploy HealthChat Desktop\":"
echo ""
for f in feed/install/macos/* feed/install/win32/x64/* feed/install/win32/arm64/*; do
name=$(basename "$f")
# Skip the stable "latest" aliases — they track whatever is deployed, not this version.
case "$name" in HealthChat.dmg|HealthChat-Setup.exe) continue ;; esac
dir=$(dirname "${f#feed/}")
case "$dir" in
*/macos) platform="macOS" ;;
*/x64) platform="Windows x64" ;;
*/arm64) platform="Windows arm64" ;;
*) platform="$dir" ;;
esac
enc=$(jq -rn --arg s "$name" '$s|@uri')
echo "- [$name]($BASE/$dir/$enc) — $platform"
done
} > release-body.md
gh release edit "$TAG" --notes-file release-body.md
echo "::notice::Release notes now link the public installers under $BASE/install/."
+12 -2
View File
@@ -62,7 +62,17 @@ jobs:
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Resolved version: $VERSION"
# `:latest` is what the ansible inventory deploys from, so only a clean
# X.Y.Z tag push may move it — never an -rc/-hotfix/-dryrun tag, and never
# a manual re-run. Otherwise a release candidate or a pipeline test would
# become production on the next deploy of any kind.
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ && "$GITHUB_EVENT_NAME" == "push" ]]; then
IS_STABLE=true
else
IS_STABLE=false
fi
echo "is_stable=$IS_STABLE" >> "$GITHUB_OUTPUT"
echo "Resolved version: $VERSION (moves :latest: $IS_STABLE)"
- name: Resolve workspace ref
id: workspace
@@ -157,7 +167,7 @@ jobs:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}
tags: |
type=raw,value=${{ steps.version.outputs.version }}
type=raw,value=stable
type=raw,value=latest,enable=${{ steps.version.outputs.is_stable == 'true' }}
type=raw,value=${{ github.sha }}
- name: Set up Docker Buildx
@@ -0,0 +1,166 @@
name: Deploy HealthChat Desktop
# Publishes an already-released desktop build to packages.healthchat.ch/desktop/ over FTPS.
#
# This is the DEPLOY half of the desktop pipeline, deliberately split from the release half
# (build-healthchat-desktop.yaml). Cutting a `healthchat-v*` tag builds, signs and stages the
# update feed + public installers at packages.healthchat.ch/desktop-releases/<version>/, but
# never touches the live feed. Nothing reaches installed apps until this workflow is dispatched
# by hand.
#
# Why that split matters: the feed under .../desktop/update/ is what the Squirrel
# auto-updater polls. Writing to it pushes an update to every installed client, so it is a
# deployment, not a publication step — the same reason the ansible playbook's deploy is a manual
# dispatch rather than a release trigger.
#
# The versioned archive directories persist on the server, so any staged version can be
# re-published without rebuilding or re-signing — that is the rollback path.
on:
workflow_dispatch:
inputs:
version:
description: "Version to publish (e.g. 1.5.0). Must have a staged feed at packages.healthchat.ch/desktop-releases/<version>/ (produced by Build HealthChat Desktop)."
required: true
type: string
dry_run:
description: "Dry run — list what would be uploaded without writing to the server."
required: false
type: boolean
default: false
permissions: {} # the feed comes from the packages.healthchat.ch archive, not from GitHub
concurrency:
# Never let two runs mirror onto the same FTP tree at once.
group: deploy-healthchat-desktop
cancel-in-progress: false
jobs:
deploy:
name: Publish update feed to packages.healthchat.ch
runs-on: ubuntu-latest
# Fetches ~2 GiB from the archive and uploads it to the live feed.
timeout-minutes: 60
steps:
- name: Resolve version
id: version
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
VERSION="$INPUT_VERSION"
# Same regex as the release workflows, so -rc1/-hotfix1 builds can be published
# deliberately (e.g. to a test machine) but never by accident.
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
echo "::error::Invalid version '$VERSION' — expected e.g. 1.5.0 or 1.5.0-rc1"
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=healthchat-v$VERSION" >> "$GITHUB_OUTPUT"
echo "Publishing HealthChat Desktop $VERSION (tag healthchat-v$VERSION)"
- name: Install lftp
run: sudo apt-get update && sudo apt-get install -y lftp
- name: Fetch staged feed from the archive
env:
FTP_SERVER: ${{ secrets.FTP_SERVER }}
FTP_USERNAME: ${{ secrets.FTP_USERNAME }}
# Passed to lftp via LFTP_PASSWORD (--env-password) so it never appears on the
# command line / process list.
LFTP_PASSWORD: ${{ secrets.FTP_PASSWORD }}
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
: "${FTP_SERVER:?FTP_SERVER secret not set}"
: "${FTP_USERNAME:?FTP_USERNAME secret not set}"
: "${LFTP_PASSWORD:?FTP_PASSWORD secret not set}"
# The archive is public — probe it over HTTPS first for a clear error before
# opening the FTP session.
if ! curl -fsSI "https://packages.healthchat.ch/desktop-releases/$VERSION/update/macos/releases.json" >/dev/null 2>&1; then
echo "::error::No staged feed at https://packages.healthchat.ch/desktop-releases/$VERSION/ — run 'Build HealthChat Desktop' (workflow_dispatch, version=$VERSION) to build and stage this version, then re-run the deploy."
exit 1
fi
lftp -u "$FTP_USERNAME" --env-password "$FTP_SERVER" <<LFTP
set cmd:fail-exit true
set ftp:ssl-force true
set ftp:ssl-protect-data true
set ssl:verify-certificate true
mirror --parallel=4 --verbose /public_html/packages/desktop-releases/$VERSION/ feed/
bye
LFTP
test -d feed || { echo "::error::Archive fetch produced no feed/ directory"; exit 1; }
echo "--- feed to publish ---"
find feed -type f | sort
- name: Sanity-check the feed before publishing
run: |
set -euo pipefail
# A partial feed is worse than none: Squirrel clients poll these paths, and a
# missing manifest breaks auto-update for everyone already installed.
fail=0
for f in feed/update/macos/releases.json \
feed/update/win32/x64/RELEASES \
feed/update/win32/arm64/RELEASES; do
if [[ ! -s "$f" ]]; then echo "::error::missing or empty: $f"; fail=1; fi
done
# The macOS updater fetches the zip named in releases.json; make sure it is present.
if [[ -s feed/update/macos/releases.json ]]; then
if ! ls feed/update/macos/*-mac.zip >/dev/null 2>&1; then
echo "::error::releases.json present but no *-mac.zip payload alongside it"; fail=1
fi
fi
exit $fail
- name: Publish to packages.healthchat.ch over FTPS
env:
FTP_SERVER: ${{ secrets.FTP_SERVER }}
FTP_USERNAME: ${{ secrets.FTP_USERNAME }}
# Passed to lftp via LFTP_PASSWORD (--env-password) so it never appears on the
# command line / process list.
LFTP_PASSWORD: ${{ secrets.FTP_PASSWORD }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
: "${FTP_SERVER:?FTP_SERVER secret not set}"
: "${FTP_USERNAME:?FTP_USERNAME secret not set}"
: "${LFTP_PASSWORD:?FTP_PASSWORD secret not set}"
MIRROR_OPTS="--parallel=4 --verbose"
if [[ "$DRY_RUN" == "true" ]]; then
echo "::notice::DRY RUN — no files will be written to the server."
MIRROR_OPTS="$MIRROR_OPTS --dry-run"
fi
# mirror -R (reverse) uploads the local tree; default is no-delete, so prior
# *.nupkg are preserved (Squirrel.Windows delta history) and only the staged files
# are written/overwritten. If the host's FTPS cert is untrusted, set
# ssl:verify-certificate to no.
lftp -u "$FTP_USERNAME" --env-password "$FTP_SERVER" <<LFTP
set cmd:fail-exit true
set ftp:ssl-force true
set ftp:ssl-protect-data true
set ssl:verify-certificate true
mkdir -pf /public_html/packages/desktop
mirror -R $MIRROR_OPTS feed/ /public_html/packages/desktop/
bye
LFTP
- name: Verify published feed
if: ${{ !inputs.dry_run }}
run: |
set -euo pipefail
base="https://packages.healthchat.ch/desktop"
fail=0
for url in "$base/update/macos/releases.json" \
"$base/update/win32/x64/RELEASES" \
"$base/update/win32/arm64/RELEASES" \
"$base/install/macos/HealthChat.dmg" \
"$base/install/win32/x64/HealthChat-Setup.exe"; do
code=$(curl -fsS -o /dev/null -w '%{http_code}' -I "$url" 2>/dev/null || echo "000")
if [[ "$code" == "200" ]]; then echo "ok $code $url"; else echo "FAIL $code $url"; fail=1; fi
done
if [[ $fail -ne 0 ]]; then
echo "::error::One or more feed URLs are not reachable — auto-update may be broken."
exit 1
fi
echo "::notice::Update feed for ${{ steps.version.outputs.version }} is live. Installed apps will pick it up on their next update check."
+1 -1
View File
@@ -1 +1 @@
v0.6.0
v1.2.0
+1 -1
View File
@@ -133,4 +133,4 @@ Conventions unique to this fork that span multiple files:
`pnpm link` is **not** used (it mutates `package.json`/lockfile). Instead, the `postinstall` hook runs `scripts/pnpm-link.ts`, which reads a `.link-config` file of `DEPENDENCY=PATH` lines and replaces those `node_modules` entries with symlinks. To develop against a local SDK, add a line like `matrix-js-sdk=/abs/path/to/matrix-js-sdk` to `.link-config` (not committed) and re-run `pnpm install`.
### Releasing HealthChat
Releases are cut by pushing a `healthchat-v<semver>` tag against `release/healthchat`, which triggers `.github/workflows/build-healthchat.yaml` (web) and `build-healthchat-desktop.yaml` (desktop). Release notes come from `HEALTHCHAT_CHANGES.md` (kept separate from upstream `CHANGELOG.md`); the healthchat-workspace plugin ref is pinned in `.healthchat-workspace-version`. Full runbook: `releasing-healthchat` skill.
Releases are cut by pushing a `healthchat-v<semver>` tag against `release/healthchat`, which triggers `.github/workflows/build-healthchat.yaml` (web) and `build-healthchat-desktop.yaml` (desktop). Release notes come from `HEALTHCHAT_CHANGES.md` (kept separate from upstream `CHANGELOG.md`); the healthchat-workspace plugin ref is pinned in `.healthchat-workspace-version`. The image is published to `ghcr.io/novaloop-ag/healthchat-web/healthchat-web` as `:<version>`, `:<sha>`, and `:latest` — where `:latest` moves **only** for a clean `X.Y.Z` tag push, never for `-rc`/`-hotfix` tags or manual dispatch runs. **Deploying is a separate manual step for both halves** and needs no version bump anywhere: the web side is a `gh workflow run deploy.yml` against the ansible repo (whose inventory tracks `:latest`), and the desktop side is `deploy-healthchat-desktop.yaml`, which publishes the update feed to packages.healthchat.ch. Cutting a tag never pushes an update to installed desktop clients — `build-healthchat-desktop.yaml` only stages the feed archive at `packages.healthchat.ch/desktop-releases/<version>/`. Full runbook: `releasing-healthchat` skill.
+123
View File
@@ -7,6 +7,129 @@ in <version>` section and uses it as the body of the GitHub Release.
One section per released version, newest on top. Upstream element-hq
`CHANGELOG.md` is kept separate and untouched.
## Changes in 1.7.2 (2026-10-25)
🐛 Fixes
- Opening app.healthchat.ch on a phone or tablet now shows the HealthChat mobile
guide with App Store and Google Play links to the HealthChat app, instead of
redirecting to the healthchat.ch website. The guide is in German, like the rest
of the HealthChat web app.
- Universal Links (iOS) and App Links (Android) for app.healthchat.ch now belong to
the HealthChat app. Phones with Element or Element X installed no longer open
HealthChat links in those apps.
## Changes in 1.7.1 (2026-10-21)
🐛 Fixes
- Calls work again on IPv4-only networks (home lines, mobile hotspots). The desktop
and web apps no longer fail with MISSING_MATRIX_RTC_TRANSPORT when the
healthchat.ch well-known lookup is blocked by a redirect without CORS headers;
the embedded Element Call now carries the LiveKit service URL as a fallback.
## Changes in 1.7.0 (2026-09-28)
🙌 Improvements
- Direct-chat calls can now use Element Call, so calls to users on the HealthChat
Android app connect instead of failing as unsupported. The desktop app now
offers the same "Call" / "Call (Legacy)" choice as HealthChat Web.
## Changes in 1.6.0 (2026-07-29)
🙌 Improvements
- Upgraded the bundled HealthChat workspace plugin to v1.2.0: the user directory
search is now gated on approval, so unapproved accounts no longer appear in
search results.
🧹 Internal
- Desktop installers and the auto-update feed are now hosted at
`https://packages.healthchat.ch/desktop/`; the old address is retired. Apps on
version 1.5.0 or older do not check the new address — download the current
installer once from the new URL, and updates resume automatically afterwards.
## Changes in 1.5.0 (2026-07-28)
🙌 Improvements
- Upgraded the bundled HealthChat workspace plugin to v1.1.0:
- Space administrators can now manage the members of a space directly from the
workspace panel.
- Removing a member from a space now also removes them from the bot-owned rooms
inside that space, instead of leaving them behind.
- The admin area shows the version of the deployed API.
- The registration form now mentions the SHPC register alongside the diploma
upload.
- Granting the Verwalter role now force-joins the space administrator, fixing
cases where the grant appeared to have no effect.
## Changes in 1.4.1 (2026-07-14)
🧹 Internal
- Consolidated the "HealthChat Light" theme into a single source of truth
(`apps/web/element.io/healthchat/HealthChat-Light-Theme.json`) that is synced into the
web and desktop configs, with a lint check that fails on drift. No user-visible change.
## Changes in 1.4.0 (2026-07-14)
🙌 Improvements
- Upgraded the bundled HealthChat workspace plugin to v1.0.0. The plugin now
authenticates against the Matrix backend through the MAS Admin API instead of
an appservice token.
## Changes in 1.3.1 (2026-07-10)
🙌 Improvements
- The Help & About settings tab now has a Sign out button, so you can log out
without leaving the settings screen.
## Changes in 1.3.0 (2026-07-08)
🙌 Improvements
- The Help & About tab is back in User Settings, in a slimmed-down form that
shows the app version and support links.
- The space hierarchy list now uses the shared theme tokens for its hover and
separator colours, so it blends in with the rest of the UI.
## Changes in 1.2.5 (2026-07-01)
🙌 Improvements
- The desktop app's Help link now points at healthchat.ch.
## Changes in 1.2.4 (2026-07-01)
🙌 Improvements
- The Windows installer splash now shows the HealthChat logo instead of the
Element icon.
- The desktop About panel now shows the HealthChat brand (author, homepage, and
description).
- Help links now point at healthchat.ch.
## Changes in 1.2.3 (2026-06-30)
🐛 Fixes
- The desktop app (macOS) now lets you interact with the Mitglieder and Admin
pages again. Their inputs and buttons were dead because the window-drag region
used for the frameless title bar swallowed all mouse and keyboard events on
those pages.
🙌 Improvements
- Share links and QR codes now resolve against `to.healthchat.ch` instead of the
public matrix.to.
- The desktop About panel and macOS app menu now show the HealthChat brand
instead of "Element".
## Changes in 1.2.2 (2026-06-30)
🐛 Fixes
Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

+27 -10
View File
@@ -48,8 +48,12 @@ feeds it into build_desktop_prepare.yaml (`config: element.io/healthchat`,
`webapp-artifact: healthchat-webapp`) and the per-platform build_desktop_{macos,windows}.yaml
reusables. It runs on every `healthchat-v*` tag (alongside the web build) and via
workflow_dispatch. The reusables are pointed at THIS fork (`repository`/`ref` inputs) so the
Electron shell, icons and the healthchat:// deep-link handler are fork-branded. On a tag push the
`release` job attaches the .dmg / .zip / .exe / .msi to the GitHub Release.
Electron shell, icons and the healthchat:// deep-link handler are fork-branded. The `release`
job uploads the staged update feed + installers to the versioned archive at
packages.healthchat.ch/desktop-releases/<ver>/ (also on workflow_dispatch — the recovery path
for a missing archive) and, on tag pushes only, links the public installer URLs in the Release
notes. Nothing binary is attached to the GitHub Release (the compressed feed outgrew GitHub's
2 GiB per-asset cap at 1.6.0).
(Linux is not built: the upstream build_desktop_linux.yaml needs a build-env Docker image
keyed by commit SHA that only exists in the upstream element-hq registry, so it can't run here.)
@@ -65,6 +69,12 @@ Regenerate after a logo change with ImageMagick:
magick apps/web/res/vector-icons/1024.png -define icon:auto-resize=16,24,32,48,64,128,256 apps/desktop/build/icon.ico
cp apps/web/res/vector-icons/1024.png apps/desktop/build/icon.icon/Assets/healthchat.png
The Windows installer splash (Squirrel loadingGif, auto-discovered by
electron-builder at build/install-spinner.gif) is a static HealthChat logo on
white. Regenerate it alongside the icons:
magick -size 320x320 xc:white \( apps/web/res/vector-icons/1024.png -resize 240x240 \) \
-gravity center -composite apps/desktop/build/install-spinner.gif
Signing (required for trusted installers)
-----------------------------------------
The macos/windows jobs run with sign: true, which gates on these GitHub environments. Create
@@ -92,14 +102,14 @@ runs can reach the signing credentials.
Auto-update + public installer hosting
--------------------------------------
The apps self-update over a feed hosted on healthchat.ch (the GitHub Release is private to this
internal fork, so it can serve neither end users nor the Squirrel auto-updater). Both the update
feed and the public installers live under https://healthchat.ch/packages/desktop/ (FTP web root
/public_html/healthchat.ch maps to https://healthchat.ch/; no packages. subdomain, so it's a path):
The apps self-update over a feed hosted on packages.healthchat.ch (the GitHub Release is private
to this internal fork, so it can serve neither end users nor the Squirrel auto-updater). Both the
update feed and the public installers live under https://packages.healthchat.ch/desktop/ (the
subdomain's docroot on the healthchat.ch FTP account is /public_html/packages):
update/ the Squirrel feed the installed apps poll hourly. config.json sets
"update_base_url": "https://healthchat.ch/packages/desktop/update/"
and the macos job passes base-url: https://healthchat.ch/packages/desktop so the
"update_base_url": "https://packages.healthchat.ch/desktop/update/"
and the macos job passes base-url: https://packages.healthchat.ch/desktop so the
generated releases.json points the *-mac.zip at this origin.
update/macos/releases.json + HealthChat-<ver>-mac.zip (Squirrel.Mac)
update/win32/<arch>/RELEASES + HealthChat-<ver>-full.nupkg (Squirrel.Windows; x64 + arm64)
@@ -107,14 +117,21 @@ feed and the public installers live under https://healthchat.ch/packages/desktop
install/macos/HealthChat-<ver>.dmg + HealthChat.dmg (stable "latest")
install/win32/<arch>/HealthChat-Setup-<ver>.exe + HealthChat-Setup.exe (stable) + *.msi
The `release` job in build-healthchat-desktop.yaml stages these trees and uploads them via lftp/FTPS
(mirror -R, no-delete so prior nupkgs survive for delta history). Add these repo secrets:
The `release` job in build-healthchat-desktop.yaml stages these trees and uploads them to the
versioned archive desktop-releases/<ver>/ (sibling of desktop/ under /public_html/packages);
the manually dispatched deploy-healthchat-desktop.yaml mirrors that archive onto the live
desktop/ tree via lftp/FTPS (mirror -R, no-delete so prior nupkgs survive for delta history).
Add these repo secrets:
FTP_SERVER hostname only (explicit FTPS / AUTH TLS on port 21; if the host cert is untrusted,
flip ssl:verify-certificate to no in the workflow)
FTP_USERNAME
FTP_PASSWORD
Caveats:
- Builds <= 1.5.0 polled the retired feed at https://healthchat.ch/packages/desktop/, which
is frozen at 1.5.0 — those installs never see newer versions. Users on them must download
the current installer once from the packages.healthchat.ch install/ URL; updates resume
automatically afterwards.
- First install is manual. Apps already installed without update_base_url (<= 1.1.2) cannot
self-update to the first build that carries it; users must download once from the install/ URL.
Every release after that updates automatically.
@@ -2,6 +2,6 @@
"appId": "ch.healthchat.desktop",
"name": "healthchat-desktop",
"productName": "HealthChat",
"description": "HealthChat: secure communication for healthcare",
"description": "Die sichere Chat-Lösung für Schweizer HealthProfessionals",
"protocols": ["ch.healthchat.desktop", "healthchat"]
}
+22 -33
View File
@@ -8,40 +8,26 @@
"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": {},
"features": { "feature_group_calls": true },
"help_url": "https://www.healthchat.ch",
"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"
},
"jitsi": { "preferred_domain": "jitsi.healthchat.ch" },
"logout_redirect_url": "",
"permalink_prefix": "https://matrix.to",
"room_directory": {
"servers": ["matrix.org"]
},
"sso_redirect_options": {
"immediate": true,
"on_welcome_page": true,
"on_login_page": true
},
"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 },
"setting_defaults": {
"UIFeature.deactivate": false,
"UIFeature.advancedEncryption": false,
@@ -51,7 +37,6 @@
"UIFeature.advancedSettings": false,
"UIFeature.feedback": false,
"custom_themes": [
"HealthChat Light",
{
"name": "HealthChat Light",
"is_dark": false,
@@ -79,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",
@@ -96,10 +88,7 @@
]
},
"show_labs_settings": false,
"update_base_url": "https://healthchat.ch/packages/desktop/update/",
"update_base_url": "https://packages.healthchat.ch/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" }
}
+7 -7
View File
@@ -3,13 +3,13 @@
"productName": "Element",
"main": "lib/electron-main.js",
"exports": "./lib/electron-main.js",
"version": "1.2.2",
"version": "1.7.2",
"description": "Element: the future of secure communication",
"author": {
"name": "Element",
"email": "support@element.io"
"name": "HealthChat",
"email": "support@healthchat.ch"
},
"homepage": "https://element.io",
"homepage": "https://www.healthchat.ch",
"repository": {
"type": "git",
"url": "https://github.com/vector-im/element-web"
@@ -85,12 +85,12 @@
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"@vitest/coverage-v8": "catalog:",
"app-builder-lib": "26.11.1",
"app-builder-lib": "26.16.1",
"chokidar": "^5.0.0",
"detect-libc": "^2.0.0",
"electron": "42.2.0",
"electron-builder": "26.11.1",
"electron-builder-squirrel-windows": "26.11.1",
"electron-builder": "26.16.1",
"electron-builder-squirrel-windows": "26.16.1",
"electron-devtools-installer": "^4.0.0",
"eslint": "^8.26.0",
"eslint-config-google": "^0.14.0",
+8
View File
@@ -310,6 +310,14 @@ app.on("ready", async () => {
return;
}
// Drive the native "About" panel from the configured brand so it shows
// "HealthChat" rather than the upstream Electron/bundle default.
const brand = global.vectorConfig.brand || "Element";
app.setAboutPanelOptions({
applicationName: brand,
applicationVersion: app.getVersion(),
});
if (argv["devtools"]) {
try {
const { installExtension, REACT_DEVELOPER_TOOLS } = await import("electron-devtools-installer");
+6
View File
@@ -106,6 +106,12 @@ export function setupMacosTitleBar(window: BrowserWindow): void {
iframe {
-webkit-app-region: no-drag;
}
/* HealthChat: exclude the page mounted in the room-view wrapper (incl. module
location renderers like the Angular custom elements) so their inputs/buttons
receive mouse events instead of being swallowed by the window-drag region */
.mx_RoomView_wrapper > * {
-webkit-app-region: no-drag;
}
/* Add a bar above room header + left panel */
+4 -2
View File
@@ -141,14 +141,16 @@ export function buildMenuTemplate(): Menu {
// macOS has specific menu conventions...
if (isMac) {
// XXX: vectorConfig won't have defaults applied to it so we need to duplicate them here
const brand = global.vectorConfig?.brand || app.name;
template.unshift({
// first macOS menu is the name of the app
role: "appMenu",
label: app.name,
label: brand,
submenu: [
{
role: "about",
label: _t("common|about") + " " + app.name,
label: _t("common|about") + " " + brand,
},
{ type: "separator" },
{
+24 -44
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": {},
"features": { "feature_group_calls": true },
"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": "",
"permalink_prefix": "https://matrix.to",
"room_directory": {
"servers": [
"matrix.org"
]
},
"sso_redirect_options": {
"immediate": true,
"on_welcome_page": true,
"on_login_page": true
},
"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 },
"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",
-60
View File
@@ -1,64 +1,4 @@
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "im.vector.app.debug",
"sha256_cert_fingerprints": [
"B0:B0:51:DC:56:5C:81:2F:E1:7F:6F:3E:94:5B:4D:79:04:71:23:AB:0D:A6:12:86:76:9E:B2:94:91:97:13:0E"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "im.vector.app.nightly",
"sha256_cert_fingerprints": [
"CA:D3:85:16:84:3A:05:CC:EB:00:AB:7B:D3:80:0F:01:BA:8F:E0:4B:38:86:F3:97:D8:F7:9A:1B:C4:54:E4:0F"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "im.vector.app",
"sha256_cert_fingerprints": [
"F3:FF:38:D2:E5:A6:38:84:86:4A:4E:0D:45:C5:3B:19:8E:7E:39:C0:50:5B:D9:63:F5:55:D6:53:2D:EA:BF:5F"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "io.element.android.x.debug",
"sha256_cert_fingerprints": [
"B0:B0:51:DC:56:5C:81:2F:E1:7F:6F:3E:94:5B:4D:79:04:71:23:AB:0D:A6:12:86:76:9E:B2:94:91:97:13:0E"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "io.element.android.x.nightly",
"sha256_cert_fingerprints": [
"CA:D3:85:16:84:3A:05:CC:EB:00:AB:7B:D3:80:0F:01:BA:8F:E0:4B:38:86:F3:97:D8:F7:9A:1B:C4:54:E4:0F"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "io.element.android.x",
"sha256_cert_fingerprints": [
"C6:DB:9B:9C:8C:BD:D6:5D:16:E8:EC:8C:8B:91:C8:31:B9:EF:C9:5C:BF:98:AE:41:F6:A9:D8:35:15:1A:7E:16"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
+2 -6
View File
@@ -3,9 +3,7 @@
"details": [
{
"appIDs": [
"7J4U792NQT.im.vector.app",
"7J4U792NQT.io.element.elementx",
"7J4U792NQT.io.element.elementx.nightly"
"6G37K72W82.ch.healthchat.element"
],
"components": [
{
@@ -25,9 +23,7 @@
},
"webcredentials": {
"apps": [
"7J4U792NQT.im.vector.app",
"7J4U792NQT.io.element.elementx",
"7J4U792NQT.io.element.elementx.nightly"
"6G37K72W82.ch.healthchat.element"
]
}
}
@@ -223,7 +223,7 @@ Please see LICENSE files in the repository root for full details.
&:hover,
&:focus-within {
background-color: $spacePanel-bg-color;
background-color: var(--cpd-color-bg-subtle-secondary);
.mx_AccessibleButton {
visibility: visible;
@@ -245,7 +245,7 @@ Please see LICENSE files in the repository root for full details.
&::before {
content: "";
position: absolute;
background-color: $spacePanel-bg-color;
background-color: var(--cpd-color-gray-400);
width: 1px;
height: 100%;
left: 6px;
+10
View File
@@ -0,0 +1,10 @@
{
"matrix_rtc_session": {
"wait_for_key_rotation_ms": 5000,
"delayed_leave_event_restart_ms": 4000,
"delayed_leave_event_delay_ms": 18000
},
"livekit": {
"livekit_service_url": "https://matrix.healthchat.ch/livekit-jwt-service"
}
}
@@ -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");
}
+6
View File
@@ -70,6 +70,12 @@ export interface IConfigOptions {
};
permalink_prefix?: string;
/**
* Base URL of a matrix.to-compatible resolver to use for Share links and QR codes,
* instead of the public `https://matrix.to`. Must use the matrix.to URL format
* (`/#/<entity>`), e.g. a self-hosted matrix.to fork.
*/
matrix_to_url?: string;
update_base_url?: string;
desktop_builds: {
+1 -1
View File
@@ -22,7 +22,7 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
auth_header_logo_url: "themes/element/img/logos/element-logo.png",
welcome_background_url: "themes/element/img/backgrounds/lake.jpg",
},
help_url: "https://element.io/help",
help_url: "https://www.healthchat.ch",
help_encryption_url: "https://element.io/help#encryption",
help_key_storage_url: "https://element.io/help#encryption5",
integrations_ui_url: "https://scalar.vector.im/",
@@ -20,6 +20,7 @@ import SidebarIcon from "@vector-im/compound-design-tokens/assets/web/icons/side
import MicOnIcon from "@vector-im/compound-design-tokens/assets/web/icons/mic-on";
import LockIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock";
import BlockIcon from "@vector-im/compound-design-tokens/assets/web/icons/block";
import HelpIcon from "@vector-im/compound-design-tokens/assets/web/icons/help";
import TabbedView, { Tab, useActiveTabWithDefault } from "../../structures/TabbedView";
import { _t, _td } from "../../../languageHandler";
@@ -31,6 +32,7 @@ import NotificationUserSettingsTab from "../settings/tabs/user/NotificationUserS
import PreferencesUserSettingsTab from "../settings/tabs/user/PreferencesUserSettingsTab";
import VoiceUserSettingsTab from "../settings/tabs/user/VoiceUserSettingsTab";
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
import HelpUserSettingsTab from "../settings/tabs/user/HelpUserSettingsTab";
import { UIFeature } from "../../../settings/UIFeature";
import BaseDialog from "./BaseDialog";
import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab";
@@ -253,8 +255,7 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {
),
);
}
// JP: Disable help tab for now
/* tabs.push(
tabs.push(
new Tab(
UserTab.Help,
_td("setting|help_about|title"),
@@ -262,7 +263,7 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {
<HelpUserSettingsTab />,
"UserSettingsHelpAbout",
),
);*/
);
return tabs as NonEmptyArray<Tab<UserTab>>;
};
@@ -6,9 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type ReactNode } from "react";
import React, { type JSX } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { type EmptyObject } from "matrix-js-sdk/src/matrix";
import SignOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/sign-out";
import AccessibleButton from "../../../elements/AccessibleButton";
import { _t } from "../../../../../languageHandler";
@@ -21,7 +22,9 @@ import { SettingsSection } from "../../shared/SettingsSection";
import { SettingsSubsection, SettingsSubsectionText } from "../../shared/SettingsSubsection";
import ExternalLink from "../../../elements/ExternalLink";
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
import { BugReportDialogButton } from "../../../elements/BugReportDialogButton";
import LogoutDialog, { shouldShowLogoutDialog } from "../../../dialogs/LogoutDialog";
import Modal from "../../../../../Modal";
import defaultDispatcher from "../../../../../dispatcher/dispatcher";
interface IState {
appVersion: string | null;
@@ -67,6 +70,14 @@ export default class HelpUserSettingsTab extends React.Component<EmptyObject, IS
};
}
private onSignOut = async (): Promise<void> => {
if (await shouldShowLogoutDialog(this.context)) {
Modal.createDialog(LogoutDialog);
} else {
defaultDispatcher.dispatch({ action: "logout" });
}
};
private onClearCacheAndReload = (): void => {
if (!PlatformPeg.get()) return;
@@ -79,118 +90,6 @@ export default class HelpUserSettingsTab extends React.Component<EmptyObject, IS
});
};
private renderLegal(): ReactNode {
const tocLinks = SdkConfig.get().terms_and_conditions_links;
if (!tocLinks) return null;
const legalLinks: JSX.Element[] = [];
for (const tocEntry of tocLinks) {
legalLinks.push(
<div key={tocEntry.url}>
<ExternalLink href={tocEntry.url}>{tocEntry.text}</ExternalLink>
</div>,
);
}
return (
<SettingsSubsection heading={_t("common|legal")}>
<SettingsSubsectionText>{legalLinks}</SettingsSubsectionText>
</SettingsSubsection>
);
}
private renderCredits(): JSX.Element {
// Note: This is not translated because it is legal text.
// Also, &nbsp; is ugly but necessary.
return (
<SettingsSubsection heading={_t("common|credits")}>
<SettingsSubsectionText>
<ul>
<li>
{_t(
"credits|default_cover_photo",
{},
{
photo: (sub) => (
<ExternalLink
href="themes/element/img/backgrounds/lake.jpg"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
author: (sub) => (
<ExternalLink href="https://www.flickr.com/golan">{sub}</ExternalLink>
),
terms: (sub) => (
<ExternalLink
href="https://creativecommons.org/licenses/by-sa/4.0/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
},
)}
</li>
<li>
{_t(
"credits|twemoji_colr",
{},
{
colr: (sub) => (
<ExternalLink
href="https://github.com/matrix-org/twemoji-colr"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
author: (sub) => <ExternalLink href="https://mozilla.org">{sub}</ExternalLink>,
terms: (sub) => (
<ExternalLink
href="https://www.apache.org/licenses/LICENSE-2.0"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
},
)}
</li>
<li>
{_t(
"credits|twemoji",
{},
{
twemoji: (sub) => (
<ExternalLink href="https://twemoji.twitter.com/">{sub}</ExternalLink>
),
author: (sub) => (
<ExternalLink href="https://twemoji.twitter.com/">{sub}</ExternalLink>
),
terms: (sub) => (
<ExternalLink
href="https://creativecommons.org/licenses/by/4.0/"
rel="noreferrer noopener"
target="_blank"
>
{sub}
</ExternalLink>
),
},
)}
</li>
</ul>
</SettingsSubsectionText>
</SettingsSubsection>
);
}
private getVersionTextToCopy = (): string => {
const { appVersion, cryptoVersion } = this.getVersionInfo();
return `${appVersion}\n${cryptoVersion}`;
@@ -214,42 +113,21 @@ export default class HelpUserSettingsTab extends React.Component<EmptyObject, IS
updateButton = <UpdateCheckButton />;
}
let bugReportingSection;
if (SdkConfig.get().bug_report_endpoint_url) {
bugReportingSection = (
<SettingsSubsection
heading={_t("bug_reporting|title")}
description={
<>
<SettingsSubsectionText>{_t("bug_reporting|introduction")}</SettingsSubsectionText>
{_t("bug_reporting|description")}
</>
}
>
<BugReportDialogButton />
<SettingsSubsectionText>
{_t(
"bug_reporting|matrix_security_issue",
{},
{
a: (sub) => (
<ExternalLink href="https://matrix.org/security-disclosure-policy/">
{sub}
</ExternalLink>
),
},
)}
</SettingsSubsectionText>
</SettingsSubsection>
);
}
const { appVersion, cryptoVersion } = this.getVersionInfo();
return (
<SettingsTab>
<SettingsSection>
{bugReportingSection}
<SettingsSubsection heading={_t("settings|account|title")}>
<AccessibleButton onClick={this.onSignOut} kind="danger_outline">
<SignOutIcon
className="mx_UserProfileSettings_accountmanageIcon"
width="24"
height="24"
/>
{_t("action|logout")}
</AccessibleButton>
</SettingsSubsection>
<SettingsSubsection heading={_t("common|faq")} description={faqText} />
<SettingsSubsection heading={_t("setting|help_about|versions")}>
<SettingsSubsectionText>
@@ -262,8 +140,6 @@ export default class HelpUserSettingsTab extends React.Component<EmptyObject, IS
{updateButton}
</SettingsSubsectionText>
</SettingsSubsection>
{this.renderLegal()}
{this.renderCredits()}
<SettingsSubsection heading={_t("common|advanced")}>
<SettingsSubsectionText>
{_t(
+1 -1
View File
@@ -12,7 +12,7 @@ import ToastStore from "../stores/ToastStore";
import SdkConfig from "../SdkConfig";
const onAccept = (): void => {
window.location.href = "https://healthchat.ch";
window.location.href = "mobile_guide/";
};
const onReject = (): void => {
@@ -7,10 +7,27 @@ Please see LICENSE files in the repository root for full details.
*/
import PermalinkConstructor, { PermalinkParts } from "./PermalinkConstructor";
import SdkConfig from "../../SdkConfig";
export const host = "matrix.to";
export const baseUrl = `https://${host}`;
export const baseUrlPattern = `^(?:https?://)?${host.replace(".", "\\.")}/#/(.*)`;
const DEFAULT_BASE_URL = "https://matrix.to";
/**
* The base URL of the matrix.to-compatible resolver to generate permalinks against.
* Defaults to the public `https://matrix.to`, but can be pointed at a self-hosted
* matrix.to fork via the `matrix_to_url` config option. Read lazily so the value
* reflects the runtime config rather than module-load time.
*/
export function baseUrl(): string {
return SdkConfig.get("matrix_to_url") ?? DEFAULT_BASE_URL;
}
export function host(): string {
return new URL(baseUrl()).host;
}
export function baseUrlPattern(): string {
return `^(?:https?://)?${host().replaceAll(".", "\\.")}/#/(.*)`;
}
/**
* Generates matrix.to permalinks
@@ -21,23 +38,23 @@ export default class MatrixToPermalinkConstructor extends PermalinkConstructor {
}
public forEvent(roomId: string, eventId: string, serverCandidates: string[]): string {
return `${baseUrl}/#/${roomId}/${eventId}${this.encodeServerCandidates(serverCandidates)}`;
return `${baseUrl()}/#/${roomId}/${eventId}${this.encodeServerCandidates(serverCandidates)}`;
}
public forRoom(roomIdOrAlias: string, serverCandidates: string[]): string {
return `${baseUrl}/#/${roomIdOrAlias}${this.encodeServerCandidates(serverCandidates)}`;
return `${baseUrl()}/#/${roomIdOrAlias}${this.encodeServerCandidates(serverCandidates)}`;
}
public forUser(userId: string): string {
return `${baseUrl}/#/${userId}`;
return `${baseUrl()}/#/${userId}`;
}
public forEntity(entityId: string): string {
return `${baseUrl}/#/${entityId}`;
return `${baseUrl()}/#/${entityId}`;
}
public isPermalinkHost(testHost: string): boolean {
return testHost === host;
return testHost === host();
}
public encodeServerCandidates(candidates: string[]): string {
@@ -52,7 +69,7 @@ export default class MatrixToPermalinkConstructor extends PermalinkConstructor {
throw new Error("Does not appear to be a permalink");
}
const matches = [...fullUrl.matchAll(new RegExp(baseUrlPattern, "gi"))][0];
const matches = [...fullUrl.matchAll(new RegExp(baseUrlPattern(), "gi"))][0];
if (!matches || matches.length < 2) {
throw new Error("Does not appear to be a permalink");
+5 -4
View File
@@ -16,6 +16,7 @@ import MatrixToPermalinkConstructor, {
baseUrl as matrixtoBaseUrl,
baseUrlPattern as matrixToBaseUrlPattern,
} from "./MatrixToPermalinkConstructor";
// `matrixtoBaseUrl` and `matrixToBaseUrlPattern` are functions (config-driven); call them at use sites.
import { type PermalinkParts } from "./PermalinkConstructor";
import type PermalinkConstructor from "./PermalinkConstructor";
import ElementPermalinkConstructor from "./ElementPermalinkConstructor";
@@ -342,13 +343,13 @@ export function tryTransformEntityToPermalink(matrixClient: MatrixClient, entity
if (permalinkParts) {
if (permalinkParts.roomIdOrAlias) {
const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : "";
let pl = matrixtoBaseUrl + `/#/${permalinkParts.roomIdOrAlias}${eventIdPart}`;
let pl = matrixtoBaseUrl() + `/#/${permalinkParts.roomIdOrAlias}${eventIdPart}`;
if (permalinkParts.viaServers?.length) {
pl += new MatrixToPermalinkConstructor().encodeServerCandidates(permalinkParts.viaServers);
}
return pl;
} else if (permalinkParts.userId) {
return matrixtoBaseUrl + `/#/${permalinkParts.userId}`;
return matrixtoBaseUrl() + `/#/${permalinkParts.userId}`;
}
}
} catch {}
@@ -437,7 +438,7 @@ export function getPrimaryPermalinkEntity(permalink: string): string | null {
*/
function getPermalinkConstructor(isPill = false): PermalinkConstructor {
const elementPrefix = SdkConfig.get("permalink_prefix");
if (elementPrefix && elementPrefix !== matrixtoBaseUrl && !isPill) {
if (elementPrefix && elementPrefix !== matrixtoBaseUrl() && !isPill) {
return new ElementPermalinkConstructor(elementPrefix);
}
@@ -448,7 +449,7 @@ export function parsePermalink(fullUrl: string): PermalinkParts | null {
try {
const elementPrefix = SdkConfig.get("permalink_prefix");
const decodedUrl = decodeURIComponent(fullUrl);
if (new RegExp(matrixToBaseUrlPattern, "i").test(decodedUrl)) {
if (new RegExp(matrixToBaseUrlPattern(), "i").test(decodedUrl)) {
return new MatrixToPermalinkConstructor().parsePermalink(decodedUrl);
} else if (fullUrl.startsWith("matrix:")) {
return new MatrixSchemePermalinkConstructor().parsePermalink(fullUrl);
+1 -1
View File
@@ -149,7 +149,7 @@ async function start(): Promise<void> {
const isAndroid = /Android/.test(navigator.userAgent);
if (isIos || isAndroid) {
if (document.cookie.indexOf("element_mobile_redirect_to_guide=false") === -1) {
window.location.href = "https://healthchat.ch";
window.location.href = "mobile_guide/";
return;
}
}
@@ -20,31 +20,31 @@
<feGaussianBlur stdDeviation="16.3415" result="effect1_foregroundBlur_382_1634"/>
</filter>
<linearGradient id="paint0_linear_382_1634" x1="141.636" y1="133.796" x2="141.636" y2="32.6829" gradientUnits="userSpaceOnUse">
<stop stop-color="#062993"/>
<stop offset="0.040404" stop-color="#02389D"/>
<stop offset="0.0808081" stop-color="#0045A6"/>
<stop offset="0.121212" stop-color="#0051AD"/>
<stop offset="0.161616" stop-color="#005DB4"/>
<stop offset="0.20202" stop-color="#0069BA"/>
<stop offset="0.242424" stop-color="#0075BB"/>
<stop offset="0.282828" stop-color="#0081BB"/>
<stop offset="0.323232" stop-color="#008CB9"/>
<stop offset="0.363636" stop-color="#0098B7"/>
<stop offset="0.40404" stop-color="#00A3B3"/>
<stop offset="0.444444" stop-color="#00AEAD"/>
<stop offset="0.484848" stop-color="#00B8A4"/>
<stop offset="0.525253" stop-color="#00C2A0"/>
<stop offset="0.565657" stop-color="#00CC99"/>
<stop offset="0.606061" stop-color="#3AD396"/>
<stop offset="0.646465" stop-color="#5DD898"/>
<stop offset="0.686869" stop-color="#79DD99"/>
<stop offset="0.727273" stop-color="#92E29B"/>
<stop offset="0.767677" stop-color="#A8E69F"/>
<stop offset="0.808081" stop-color="#BBEAA5"/>
<stop offset="0.848485" stop-color="#CDEEAE"/>
<stop offset="0.888889" stop-color="#DCF2B9"/>
<stop offset="0.929293" stop-color="#EAF6C7"/>
<stop offset="0.969697" stop-color="#F5FBD5"/>
<stop stop-color="#12325C"/>
<stop offset="0.040404" stop-color="#143561"/>
<stop offset="0.0808081" stop-color="#153966"/>
<stop offset="0.121212" stop-color="#173C6C"/>
<stop offset="0.161616" stop-color="#194071"/>
<stop offset="0.20202" stop-color="#1A4376"/>
<stop offset="0.242424" stop-color="#1C467B"/>
<stop offset="0.282828" stop-color="#1D4A80"/>
<stop offset="0.323232" stop-color="#1F4D85"/>
<stop offset="0.363636" stop-color="#225187"/>
<stop offset="0.40404" stop-color="#255588"/>
<stop offset="0.444444" stop-color="#28588A"/>
<stop offset="0.484848" stop-color="#2B5C8B"/>
<stop offset="0.525253" stop-color="#2E608D"/>
<stop offset="0.565657" stop-color="#31638E"/>
<stop offset="0.606061" stop-color="#346790"/>
<stop offset="0.646465" stop-color="#3B6D93"/>
<stop offset="0.686869" stop-color="#527E9F"/>
<stop offset="0.727273" stop-color="#698FAB"/>
<stop offset="0.767677" stop-color="#81A0B8"/>
<stop offset="0.808081" stop-color="#98B1C4"/>
<stop offset="0.848485" stop-color="#AFC2D0"/>
<stop offset="0.888889" stop-color="#C6D3DC"/>
<stop offset="0.929293" stop-color="#DEE4E9"/>
<stop offset="0.969697" stop-color="#F5F5F5"/>
</linearGradient>
<clipPath id="clip0_382_1634">
<rect width="375" height="178" fill="white"/>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

+31 -24
View File
@@ -3,11 +3,12 @@
Please note: This page is similar to the one at mobile.element.io so any changes made here can potentially
be applied to that site as well. They still share the mobile-apps.ts file for updating the app metadata.
-->
<html lang="en">
<html lang="de">
<head>
<title>Element Mobile Guide</title>
<meta charset="utf-8" />
<title>HealthChat für Smartphone und Tablet</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-itunes-app" content="app-id=id1631335820" />
<meta name="apple-itunes-app" content="app-id=id6743060078" />
</head>
<body>
@@ -19,10 +20,15 @@
<header>
<div class="mx_HomePage_header">
<div>
<img src="./assets/element-logo.svg" alt="Element Logo" width="148px" height="32px" />
<img
src="../themes/element/img/logos/element-logo.png"
alt="HealthChat Logo"
width="32px"
height="32px"
/>
</div>
<div>
<p id="header_title">Join Element.</p>
<p id="header_title">HealthChat auf dem Smartphone</p>
</div>
</div>
<hr />
@@ -31,8 +37,8 @@
<main>
<div class="mx_HomePage_col mx_Spacer">
<p>
The <a id="back_to_element_button" href="#" class="mx_DesktopLink">desktop site</a> does
<b>not</b> work on mobile, please download the app.
Die Web-Version ist für den Desktop gedacht und funktioniert auf Smartphone und Tablet
<b>nicht</b>. Für Smartphone und Tablet gibt es die HealthChat-App.
</p>
</div>
<div class="mx_HomePage_col mx_Spacer">
@@ -41,44 +47,38 @@
<span>1</span>
</div>
<div>
<h1 id="step1_heading">Download Element</h1>
<h1 id="step1_heading">HealthChat-App laden</h1>
<div class="mx_StoreLinks">
<a
href="https://apps.apple.com/app/element-x-secure-chat-call/id1631335820"
href="https://apps.apple.com/ch/app/healthchat/id6743060078"
target="_blank"
class="mx_StoreBadge"
id="app_store_link"
>
<img
src="./assets/app-store-badge.svg"
alt="Download on the App Store."
alt="Im App Store laden."
width="120px"
height="40px"
/>
</a>
<a
href="https://play.google.com/store/apps/details?id=io.element.android.x"
href="https://play.google.com/store/apps/details?id=ch.healthchat.element"
target="_blank"
class="mx_StoreBadge"
id="play_store_link"
>
<img
src="./assets/google-play-badge.svg"
alt="Get it on Google Play."
alt="Jetzt bei Google Play."
width="135px"
height="40px"
/>
</a>
</div>
<p id="f_droid_section">
Also available on
<a
href="https://f-droid.org/packages/io.element.android.x"
target="_blank"
class="mx_ClearDecoration"
id="f_droid_link"
>F-Droid</a
>
<p id="f_droid_section" style="display: none">
Auch verfügbar auf
<a href="#" target="_blank" class="mx_ClearDecoration" id="f_droid_link">F-Droid</a>
</p>
</div>
</div>
@@ -89,12 +89,19 @@
<span>2</span>
</div>
<div>
<h1>Come back here to sign in</h1>
<p id="step2_description">This will set up your app</p>
<a class="mx_Button" id="deep_link_button" href="#">Sign in</a>
<h1>Danach hier anmelden</h1>
<p id="step2_description">Damit wird die App eingerichtet</p>
<a class="mx_Button" id="deep_link_button" href="#">Anmelden</a>
</div>
</div>
</div>
<div class="mx_HomePage_col mx_Spacer">
<p>
<a id="back_to_element_button" href="#" class="mx_DesktopLink"
>Trotzdem die Web-Version öffnen</a
>
</p>
</div>
</main>
</div>
+8 -8
View File
@@ -23,8 +23,8 @@ function onBackToElementClick(): void {
// NEVER pass user-controlled content to this function! Hardcoded strings only please.
function renderConfigError(message: string): void {
const contactMsg =
"If this is unexpected, please contact your system administrator " + "or technical support representative.";
message = `<h2>Error loading Element</h2><p>${message}</p><p>${contactMsg}</p>`;
"Falls das unerwartet ist, bitte den Systemadministrator " + "oder den technischen Support kontaktieren.";
message = `<h2>HealthChat konnte nicht geladen werden</h2><p>${message}</p><p>${contactMsg}</p>`;
const toHide = document.getElementsByClassName("mx_HomePage_container");
const errorContainers = document.getElementsByClassName(
@@ -59,12 +59,12 @@ async function initPage(): Promise<void> {
const incompatibleOptions = [wkConfig, serverName, defaultHsUrl].filter((i) => !!i);
if (defaultHsUrl && (wkConfig || serverName)) {
return renderConfigError(
"Invalid configuration: a default_hs_url can't be specified along with default_server_name " +
"or default_server_config",
"Ungültige Konfiguration: default_hs_url kann nicht zusammen mit default_server_name " +
"oder default_server_config angegeben werden",
);
}
if (incompatibleOptions.length < 1) {
return renderConfigError("Invalid configuration: no default server specified.");
return renderConfigError("Ungültige Konfiguration: kein Standard-Server angegeben.");
}
let hsUrl: string | undefined;
@@ -100,7 +100,7 @@ async function initPage(): Promise<void> {
}
} else {
logger.error(e);
return renderConfigError("Unable to fetch homeserver configuration");
return renderConfigError("Homeserver-Konfiguration konnte nicht geladen werden");
}
}
}
@@ -111,7 +111,7 @@ async function initPage(): Promise<void> {
}
if (!hsUrl) {
return renderConfigError("Unable to locate homeserver");
return renderConfigError("Homeserver konnte nicht gefunden werden");
}
if (hsUrl && !hsUrl.endsWith("/")) hsUrl += "/";
@@ -131,7 +131,7 @@ async function initPage(): Promise<void> {
// Not part of updateMobilePage as the link is only shown on mobile_guide and not on mobile.element.io
document.getElementById("back_to_element_button")!.onclick = onBackToElementClick;
updateMobilePage(metadata, deepLinkUrl, serverName ?? hsUrl);
updateMobilePage(metadata, deepLinkUrl);
}
void initPage();
+12 -13
View File
@@ -38,12 +38,11 @@ export const mobileApps: Record<MobileAppVariant, MobileAppMetadata> = {
isProApp: false,
},
[MobileAppVariant.X]: {
name: "Element X",
appleAppId: "id1631335820",
appStoreUrl: "https://apps.apple.com/app/element-x-secure-chat-call/id1631335820",
playStoreUrl: "https://play.google.com/store/apps/details?id=io.element.android.x",
fDroidUrl: "https://f-droid.org/packages/io.element.android.x",
deepLinkPath: "/element",
name: "HealthChat",
appleAppId: "id6743060078",
appStoreUrl: "https://apps.apple.com/ch/app/healthchat/id6743060078",
playStoreUrl: "https://play.google.com/store/apps/details?id=ch.healthchat.element",
deepLinkPath: "",
usesLegacyDeepLink: false,
isProApp: false,
},
@@ -58,18 +57,17 @@ export const mobileApps: Record<MobileAppVariant, MobileAppMetadata> = {
},
};
export function updateMobilePage(metadata: MobileAppMetadata, deepLinkUrl: string, server: string | undefined): void {
export function updateMobilePage(metadata: MobileAppMetadata, deepLinkUrl: string): void {
const appleMeta = document.querySelector('meta[name="apple-itunes-app"]') as Element;
appleMeta.setAttribute("content", `app-id=${metadata.appleAppId}`);
if (server) {
(document.getElementById("header_title") as HTMLHeadingElement).innerText = `Join ${server} on Element`;
}
(document.getElementById("header_title") as HTMLHeadingElement).innerText = `${metadata.name} auf dem Smartphone`;
(document.getElementById("app_store_link") as HTMLAnchorElement).href = metadata.appStoreUrl;
(document.getElementById("play_store_link") as HTMLAnchorElement).href = metadata.playStoreUrl;
if (metadata.fDroidUrl) {
(document.getElementById("f_droid_link") as HTMLAnchorElement).href = metadata.fDroidUrl;
document.getElementById("f_droid_section")!.style.display = "";
} else {
document.getElementById("f_droid_section")!.style.display = "none";
}
@@ -77,11 +75,12 @@ export function updateMobilePage(metadata: MobileAppMetadata, deepLinkUrl: strin
const step1Heading = document.getElementById("step1_heading")!;
step1Heading.innerHTML = step1Heading!.innerHTML.replace("Element", metadata.name);
// Step 2 is only shown on the mobile guide, not on mobile.element.io
if (document.getElementById("step2_container")) {
// Step 2 is only shown on the mobile guide, not on mobile.element.io,
// and only for apps that offer a sign-in deep link
if (document.getElementById("step2_container") && (metadata.deepLinkPath || metadata.usesLegacyDeepLink)) {
document.getElementById("step2_container")!.style.display = "block";
if (metadata.isProApp) {
document.getElementById("step2_description")!.innerHTML = "Use your work email to join";
document.getElementById("step2_description")!.innerHTML = "Mit der Arbeits-E-Mail-Adresse anmelden";
}
(document.getElementById("deep_link_button") as HTMLAnchorElement).href = deepLinkUrl;
}
@@ -200,8 +200,7 @@ describe("<UserSettingsDialog />", () => {
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Ignored Users");
});
// HealthChat disables the Help tab "for now" (UserSettingsDialog.tsx). Restore when re-enabled.
it.skip("renders with help tab selected", () => {
it("renders with help tab selected", () => {
const { container } = render(getComponent({ initialTabId: UserTab.Help }));
expect(getActiveTabLabel(container)).toEqual("Help & About");
@@ -219,5 +219,34 @@ NodeList [
Encryption
</span>
</li>,
<li
aria-controls="mx_tabpanel_USER_HELP_TAB"
aria-selected="false"
class="mx_AccessibleButton mx_TabbedView_tabLabel"
data-testid="settings-tab-USER_HELP_TAB"
role="tab"
tabindex="-1"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 8a1.5 1.5 0 0 0-1.5 1.5 1 1 0 1 1-2 0 3.5 3.5 0 1 1 6.01 2.439q-.183.188-.352.355c-.287.288-.54.54-.76.824-.293.375-.398.651-.398.882a1 1 0 1 1-2 0c0-.874.407-1.58.819-2.11.305-.392.688-.775 1-1.085l.257-.26A1.5 1.5 0 0 0 12 8m1 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0"
/>
<path
d="M8.1 21.213A9.7 9.7 0 0 0 12 22a9.7 9.7 0 0 0 3.9-.788 10.1 10.1 0 0 0 3.175-2.137q1.35-1.35 2.137-3.175A9.7 9.7 0 0 0 22 12a9.7 9.7 0 0 0-.788-3.9 10.1 10.1 0 0 0-2.137-3.175q-1.35-1.35-3.175-2.137A9.7 9.7 0 0 0 12 2a9.7 9.7 0 0 0-3.9.788 10.1 10.1 0 0 0-3.175 2.137Q3.575 6.275 2.788 8.1A9.7 9.7 0 0 0 2 12q0 2.075.788 3.9a10.1 10.1 0 0 0 2.137 3.175q1.35 1.35 3.175 2.137m9.575-3.538Q15.35 20 12 20t-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675"
/>
</svg>
<span
class="mx_TabbedView_tabLabel_text"
id="mx_tabpanel_USER_HELP_TAB_label"
>
Help & About
</span>
</li>,
]
`;
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import MatrixToPermalinkConstructor from "../../../../src/utils/permalinks/MatrixToPermalinkConstructor";
import { PermalinkParts } from "../../../../src/utils/permalinks/PermalinkConstructor";
import SdkConfig from "../../../../src/SdkConfig";
describe("MatrixToPermalinkConstructor", () => {
const peramlinkConstructor = new MatrixToPermalinkConstructor();
@@ -49,4 +50,32 @@ describe("MatrixToPermalinkConstructor", () => {
).toEqual("https://matrix.to/#/!myroom:example.com/$event4?via=one.example.com&via=two.example.com");
});
});
describe("with a custom matrix_to_url config", () => {
beforeEach(() => {
SdkConfig.add({ matrix_to_url: "https://to.healthchat.ch" });
});
afterEach(() => {
SdkConfig.reset();
});
it("generates links against the configured host", () => {
const constructor = new MatrixToPermalinkConstructor();
expect(constructor.forUser("@user:example.com")).toEqual("https://to.healthchat.ch/#/@user:example.com");
expect(constructor.forRoom("!myroom:example.com", [])).toEqual(
"https://to.healthchat.ch/#/!myroom:example.com",
);
});
it("parses links from the configured host and rejects matrix.to links", () => {
const constructor = new MatrixToPermalinkConstructor();
expect(constructor.parsePermalink("https://to.healthchat.ch/#/@user:example.com")).toEqual(
new PermalinkParts(null, null, "@user:example.com", null),
);
expect(() => constructor.parsePermalink("https://matrix.to/#/@user:example.com")).toThrow(
new Error("Does not appear to be a permalink"),
);
});
});
});
+12
View File
@@ -728,6 +728,18 @@ export default (env: string, argv: Record<string, any>): webpack.Configuration =
context: path.join(getPackageRoot("@element-hq/element-call-embedded"), "dist"),
to: path.join(__dirname, "webapp", "widgets", "element-call"),
},
// HealthChat: overwrite the embedded Element Call config with one that carries a
// LiveKit fallback. Element Call fetches https://healthchat.ch/.well-known/matrix/client
// itself from the widget origin; over IPv4 that host is served by Wix, which answers
// a 301 to matrix.healthchat.ch without an Access-Control-Allow-Origin header, so
// the fetch fails CORS on IPv4-only networks and Element Call falls back to this
// file. Without the livekit block it errors with MISSING_MATRIX_RTC_TRANSPORT.
// Keep the matrix_rtc_session timers in sync with what the package ships.
{
from: "res/element-call-config.json",
to: path.join(__dirname, "webapp", "widgets", "element-call", "config.json"),
force: true,
},
// Mobile guide assets
{
from: "assets/**",
+4
View File
@@ -125,6 +125,10 @@ complete re-branding/private labeling, a more personalised experience can be ach
3. `brand`: Optional name for the app. Defaults to `Element`. This is used throughout the application in various strings/locations.
4. `permalink_prefix`: An optional URL pointing to an Element Web deployment. For example, `https://app.element.io`. This will
change all permalinks (via the "Share" menus) to point at the Element Web deployment rather than `matrix.to`.
4a. `matrix_to_url`: An optional base URL of a matrix.to-compatible resolver to use for Share links and QR codes instead of
the public `https://matrix.to`. Unlike `permalink_prefix` (which points at an Element Web deployment and uses Element's
`/#/room/…` URL format), this keeps the matrix.to URL format (`/#/<entity>`), so it must point at a matrix.to deployment or
fork. For example, `https://to.example.com`.
5. `desktop_builds`: Optional. Where the desktop builds for the application are, if available. This is explained in more detail
down below.
6. `mobile_builds`: Optional. Like `desktop_builds`, except for the mobile apps. Also described in more detail down below.
+77 -356
View File
@@ -422,8 +422,8 @@ importers:
specifier: 'catalog:'
version: 4.1.7(@vitest/browser@4.1.7)(vitest@4.1.7)
app-builder-lib:
specifier: 26.11.1
version: 26.11.1(dmg-builder@26.11.1)(electron-builder-squirrel-windows@26.11.1)
specifier: 26.16.1
version: 26.16.1(dmg-builder@26.16.1)(electron-builder-squirrel-windows@26.16.1)
chokidar:
specifier: ^5.0.0
version: 5.0.0
@@ -434,11 +434,11 @@ importers:
specifier: 42.2.0
version: 42.2.0
electron-builder:
specifier: 26.11.1
version: 26.11.1(electron-builder-squirrel-windows@26.11.1)
specifier: 26.16.1
version: 26.16.1(electron-builder-squirrel-windows@26.16.1)
electron-builder-squirrel-windows:
specifier: 26.11.1
version: 26.11.1(dmg-builder@26.11.1)
specifier: 26.16.1
version: 26.16.1(dmg-builder@26.16.1)
electron-devtools-installer:
specifier: ^4.0.0
version: 4.0.0
@@ -778,7 +778,7 @@ importers:
version: 1.2.7(html-webpack-plugin@5.6.7(webpack@5.107.1))(webpack@5.107.1)
'@sentry/webpack-plugin':
specifier: ^5.0.0
version: 5.3.0(encoding@0.1.13)(webpack@5.107.1)
version: 5.3.0(webpack@5.107.1)
'@stylistic/eslint-plugin':
specifier: ^5.0.0
version: 5.10.0(eslint@8.57.1)
@@ -1416,9 +1416,6 @@ importers:
packages:
7zip-bin@5.2.0:
resolution: {integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==}
'@action-validator/cli@0.6.0':
resolution: {integrity: sha512-Z8TYOK4GqUIpI0UuspUJPB6dhr0niTumhwI5iiZVqFRXm4u05bZawnFKltpvoFUfJg9mHbbIBlleqsRJAgl53Q==}
hasBin: true
@@ -2599,10 +2596,6 @@ packages:
peerDependencies:
postcss: ^8.4
'@develar/schema-utils@2.6.5':
resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==}
engines: {node: '>= 8.9.0'}
'@discoveryjs/json-ext@0.6.3':
resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==}
engines: {node: '>=14.17.0'}
@@ -2691,8 +2684,8 @@ packages:
engines: {node: '>=12.0.0'}
hasBin: true
'@electron/rebuild@4.0.3':
resolution: {integrity: sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==}
'@electron/rebuild@4.2.0':
resolution: {integrity: sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==}
engines: {node: '>=22.12.0'}
hasBin: true
@@ -3423,6 +3416,10 @@ packages:
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
engines: {node: '>= 16'}
'@noble/hashes@1.8.0':
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
engines: {node: ^14.21.3 || >=16}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -3435,18 +3432,10 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@npmcli/agent@3.0.0':
resolution: {integrity: sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==}
engines: {node: ^18.17.0 || >=20.5.0}
'@npmcli/agent@4.0.0':
resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==}
engines: {node: ^20.17.0 || >=22.9.0}
'@npmcli/fs@4.0.0':
resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==}
engines: {node: ^18.17.0 || >=20.5.0}
'@npmcli/fs@5.0.0':
resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -5885,9 +5874,6 @@ packages:
'@types/picomatch@4.0.2':
resolution: {integrity: sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==}
'@types/plist@3.0.5':
resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==}
'@types/png-chunks-extract@1.0.2':
resolution: {integrity: sha512-z6djfFIbrrddtunoMJBOPlyZrnmeuG1kkvHUNi2QfpOb+JMMLuLliHHTmMyRi7k7LiTAut0HbdGCF6ibDtQAHQ==}
@@ -5997,9 +5983,6 @@ packages:
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
'@types/verror@1.10.11':
resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==}
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
@@ -6446,6 +6429,7 @@ packages:
'@xmldom/xmldom@0.8.13':
resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
engines: {node: '>=10.0.0'}
deprecated: this version has critical issues, please update to the latest version
'@xtuc/ieee754@1.2.0':
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@@ -6473,10 +6457,6 @@ packages:
'@zxcvbn-ts/language-en@3.0.2':
resolution: {integrity: sha512-Zp+zL+I6Un2Bj0tRXNs6VUBq3Djt+hwTwUz4dkt2qgsQz47U0/XthZ4ULrT/RxjwJRl5LwiaKOOZeOtmixHnjg==}
abbrev@3.0.1:
resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==}
engines: {node: ^18.17.0 || >=20.5.0}
abbrev@4.0.0:
resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -6621,15 +6601,12 @@ packages:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
app-builder-bin@5.0.0-alpha.12:
resolution: {integrity: sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==}
app-builder-lib@26.11.1:
resolution: {integrity: sha512-KCZm58CifmTGbzw9PZOyDEecwjY3jY9cQ4uBRIDxZYhaJqqsz0RQFkryoV+EPhjVlkPql7Jsg6SNUkz837Bkhw==}
app-builder-lib@26.16.1:
resolution: {integrity: sha512-FhaO6YOup01ZfQW0Z6gt3AyukJjv1gW4uFK47jTgwcHZKqyN/fSlK2LqPf9tAeZYLP2bRJLDzeOkRImsw2X4Pg==}
engines: {node: '>=14.0.0'}
peerDependencies:
dmg-builder: 26.11.1
electron-builder-squirrel-windows: 26.11.1
dmg-builder: 26.16.1
electron-builder-squirrel-windows: 26.16.1
applescript@1.0.0:
resolution: {integrity: sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ==}
@@ -6704,10 +6681,6 @@ packages:
resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==}
engines: {node: '>=12.0.0'}
assert-plus@1.0.0:
resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==}
engines: {node: '>=0.8'}
assert@2.1.0:
resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
@@ -6771,6 +6744,9 @@ packages:
await-lock@3.0.0:
resolution: {integrity: sha512-eO6fLiSnrJrMdjWMNK8zbVRXPs2TKJg78iKZd9wDpN3na5tcoV6EoeiOlMgk2QaAQ1gIrK1YuMsJHXWqz89tSA==}
aws4@1.13.2:
resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==}
axe-core@4.11.4:
resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==}
engines: {node: '>=4'}
@@ -7077,12 +7053,12 @@ packages:
resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==}
engines: {node: '>=10.0.0'}
builder-util-runtime@9.6.1:
resolution: {integrity: sha512-gnk+lN50T7TIRp4bHTVydSnWsNkYAxGiDDbed3U26Z82s1lr8em4E4fLs/EPfIEZIbfi4Zfo5NsdA9gHWbPmXg==}
builder-util-runtime@9.7.0:
resolution: {integrity: sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==}
engines: {node: '>=12.0.0'}
builder-util@26.11.1:
resolution: {integrity: sha512-oWRbAjgN9RdVCLvNVN9ltZwaUkrH/FrH8wdggAwpz426PTdcbGEARp7J1HXad/QijURPxYL+BKykThaRjluPIQ==}
builder-util@26.16.0:
resolution: {integrity: sha512-RLyJhB7Si3YkzKR9ubQslWuXW3Vhs3CGe1i+SeixBZ0qTd1mk3XBmssvY22TlB6CS5blyko8Gu1JzpYk8UkYAg==}
engines: {node: '>=14.0.0'}
builtin-modules@3.3.0:
@@ -7108,10 +7084,6 @@ packages:
resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==}
engines: {node: '>=6.0.0'}
cacache@19.0.1:
resolution: {integrity: sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==}
engines: {node: ^18.17.0 || >=20.5.0}
cacache@20.0.4:
resolution: {integrity: sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -7257,10 +7229,6 @@ packages:
resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==}
engines: {node: '>=6'}
cli-truncate@2.1.0:
resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==}
engines: {node: '>=8'}
cli-truncate@5.2.0:
resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
engines: {node: '>=20'}
@@ -7445,9 +7413,6 @@ packages:
core-js@3.48.0:
resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==}
core-util-is@1.0.2:
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@@ -7499,9 +7464,6 @@ packages:
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
engines: {node: '>= 14'}
crc@3.8.0:
resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==}
create-ecdh@4.0.4:
resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==}
@@ -8006,14 +7968,8 @@ packages:
dir-compare@4.2.0:
resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==}
dmg-builder@26.11.1:
resolution: {integrity: sha512-uFE7wiNn/GJ/MCtHz9ZIYj8w+27WTKush6PX6a1rt7RWJ5Ua4WgK6PAgErSBQXk0Q8UM4YMTXUBNFhN1gR+1qw==}
dmg-license@1.0.11:
resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==}
engines: {node: '>=8'}
os: [darwin]
hasBin: true
dmg-builder@26.16.1:
resolution: {integrity: sha512-pnI/3Qb24Uk+rMTgIUrsVUKosVgwmBUdF8Zeb8TexOSbpq8MWc7v6l+n+FrEqVkjNZwzBN+XpDS9ENgZ/rkWAw==}
dns-packet@5.6.1:
resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==}
@@ -8150,19 +8106,19 @@ packages:
engines: {node: '>=0.12.18'}
hasBin: true
electron-builder-squirrel-windows@26.11.1:
resolution: {integrity: sha512-qWplLUSGhcjKClb1G8c9TOI5abYZGsIP7YlA6jSBLPFdSvN+teV30jFZsNgM1KYeMD6liqNZaRjySJ/zSq+X8A==}
electron-builder-squirrel-windows@26.16.1:
resolution: {integrity: sha512-w0y44wSaT1l6R7CAGmeHn4nHPfvzDyCAU1xJyi1w9SbPYJpYn76SmHDzqHf8Y7l91cPWTdPYBpGQtB2T5mJ08A==}
electron-builder@26.11.1:
resolution: {integrity: sha512-CMG8PQAnUBWIx9THpwWysrHfJQs9Q1E1VDayK+ZkNqvih+ao8mywMP5v2ohLkzlg1ZeVyvkQP6oICJGzyeCAUQ==}
electron-builder@26.16.1:
resolution: {integrity: sha512-LrLK65QX5PUYYODXqp23FKrV7CILTtVY7mrJckNknO9jLNSMiqFkKbSMiDRw4CjOADMPVDdWLxY4mezOZWswxg==}
engines: {node: '>=14.0.0'}
hasBin: true
electron-devtools-installer@4.0.0:
resolution: {integrity: sha512-9Tntu/jtfSn0n6N/ZI6IdvRqXpDyLQiDuuIbsBI+dL+1Ef7C8J2JwByw58P3TJiNeuqyV3ZkphpNWuZK5iSY2w==}
electron-publish@26.11.1:
resolution: {integrity: sha512-aMugkYS7QZtS/PjbyOSh0MlymxoWppj7+lEcqmsO1jM6igedcgAdFtmWms3g/DdMPpCZDVKxzkC4a3UK6HGlmA==}
electron-publish@26.16.0:
resolution: {integrity: sha512-Vt3KzQIiw9BImvNOYtndg9Mjki+tl4+1sQiC/+G5j8khWaENOJFWodiB+sUl6yyHwtd37avehskdtPw7f8y/+Q==}
electron-store@11.0.2:
resolution: {integrity: sha512-4VkNRdN+BImL2KcCi41WvAYbh6zLX5AUTi4so68yPqiItjbgTjqpEnGAqasgnG+lB6GuAyUltKwVopp6Uv+gwQ==}
@@ -8227,9 +8183,6 @@ packages:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
encoding@0.1.13:
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
@@ -8644,10 +8597,6 @@ packages:
engines: {node: '>= 10.17.0'}
hasBin: true
extsprintf@1.4.1:
resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==}
engines: {'0': node >=0.6.0}
fake-indexeddb@6.2.5:
resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==}
engines: {node: '>=18'}
@@ -9258,11 +9207,6 @@ packages:
resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==}
engines: {node: '>=10.18'}
iconv-corefoundation@1.1.7:
resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==}
engines: {node: ^8.11.2 || >=10}
os: [darwin]
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@@ -9927,6 +9871,7 @@ packages:
jsrsasign@11.1.3:
resolution: {integrity: sha512-nPnK5D/4lv0Dwr7TlzrKtAd8JlLZwFTqTUUB3NQCbtdobcRcohGFxjbPySDVh74iWUudcCsapYT6OxoyhJLhhA==}
deprecated: This package is no longer maintained.
jsx-ast-utils@3.3.5:
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
@@ -10231,10 +10176,6 @@ packages:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
make-fetch-happen@14.0.3:
resolution: {integrity: sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==}
engines: {node: ^18.17.0 || >=20.5.0}
make-fetch-happen@15.0.5:
resolution: {integrity: sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -10472,10 +10413,6 @@ packages:
resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==}
engines: {node: '>=16 || 14 >=14.17'}
minipass-fetch@4.0.1:
resolution: {integrity: sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==}
engines: {node: ^18.17.0 || >=20.5.0}
minipass-fetch@5.0.2:
resolution: {integrity: sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -10488,10 +10425,6 @@ packages:
resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}
engines: {node: '>=8'}
minipass-sized@1.0.3:
resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==}
engines: {node: '>=8'}
minipass-sized@2.0.0:
resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==}
engines: {node: '>=8'}
@@ -10601,9 +10534,6 @@ packages:
resolution: {integrity: sha512-Erq5w/t3syw3s4sDsUaX4QttIdBPsGKTT1DTRsCkTonGggczhlDKm/wDX3o+HPJpQ41EjXCbcmXf0tgr5YZJXw==}
engines: {node: '>=22.12.0'}
node-addon-api@1.7.2:
resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
node-api-version@0.2.1:
resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==}
@@ -10616,11 +10546,6 @@ packages:
encoding:
optional: true
node-gyp@11.5.0:
resolution: {integrity: sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==}
engines: {node: ^18.17.0 || >=20.5.0}
hasBin: true
node-gyp@12.2.0:
resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -10639,11 +10564,6 @@ packages:
non-layered-tidy-tree-layout@2.0.2:
resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==}
nopt@8.1.0:
resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==}
engines: {node: ^18.17.0 || >=20.5.0}
hasBin: true
nopt@9.0.0:
resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -11552,10 +11472,6 @@ packages:
resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
proc-log@5.0.0:
resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==}
engines: {node: ^18.17.0 || >=20.5.0}
proc-log@6.1.0:
resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -12302,10 +12218,6 @@ packages:
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
engines: {node: '>=14.16'}
slice-ansi@3.0.0:
resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==}
engines: {node: '>=8'}
slice-ansi@4.0.0:
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
engines: {node: '>=10'}
@@ -12411,10 +12323,6 @@ packages:
ssim.js@3.5.0:
resolution: {integrity: sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==}
ssri@12.0.0:
resolution: {integrity: sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==}
engines: {node: ^18.17.0 || >=20.5.0}
ssri@13.0.1:
resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -13117,14 +13025,6 @@ packages:
resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==}
engines: {node: '>=20'}
unique-filename@4.0.0:
resolution: {integrity: sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==}
engines: {node: ^18.17.0 || >=20.5.0}
unique-slug@5.0.0:
resolution: {integrity: sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==}
engines: {node: ^18.17.0 || >=20.5.0}
unist-util-is@6.0.1:
resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
@@ -13299,10 +13199,6 @@ packages:
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
verror@1.10.1:
resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==}
engines: {node: '>=0.6.0'}
vfile-message@4.0.3:
resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
@@ -13827,8 +13723,6 @@ packages:
snapshots:
7zip-bin@5.2.0: {}
'@action-validator/cli@0.6.0(@action-validator/core@0.6.0)':
dependencies:
'@action-validator/core': 0.6.0
@@ -15239,11 +15133,6 @@ snapshots:
dependencies:
postcss: 8.5.15
'@develar/schema-utils@2.6.5':
dependencies:
ajv: 6.15.0
ajv-keywords: 3.5.2(ajv@6.15.0)
'@discoveryjs/json-ext@0.6.3': {}
'@discoveryjs/json-ext@1.0.0': {}
@@ -15378,21 +15267,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@electron/rebuild@4.0.3':
'@electron/rebuild@4.2.0':
dependencies:
'@malept/cross-spawn-promise': 2.0.0
debug: 4.4.3
detect-libc: 2.1.2
got: 11.8.6
graceful-fs: 4.2.11
node-abi: 4.31.0
node-api-version: 0.2.1
node-gyp: 11.5.0
ora: 5.3.0
node-gyp: 12.2.0
read-binary-file-arch: 1.0.6
semver: 7.8.1
tar: 7.5.15
yargs: 17.7.2
transitivePeerDependencies:
- supports-color
@@ -16257,6 +16139,8 @@ snapshots:
'@noble/hashes@1.4.0': {}
'@noble/hashes@1.8.0': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -16269,16 +16153,6 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1
'@npmcli/agent@3.0.0':
dependencies:
agent-base: 7.1.4
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
lru-cache: 10.4.3
socks-proxy-agent: 8.0.5
transitivePeerDependencies:
- supports-color
'@npmcli/agent@4.0.0':
dependencies:
agent-base: 7.1.4
@@ -16289,10 +16163,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@npmcli/fs@4.0.0':
dependencies:
semver: 7.8.1
'@npmcli/fs@5.0.0':
dependencies:
semver: 7.8.1
@@ -17820,11 +17690,11 @@ snapshots:
'@sentry-internal/replay-canvas': 10.53.1
'@sentry/core': 10.53.1
'@sentry/bundler-plugin-core@5.3.0(encoding@0.1.13)':
'@sentry/bundler-plugin-core@5.3.0':
dependencies:
'@babel/core': 7.29.0
'@sentry/babel-plugin-component-annotate': 5.3.0
'@sentry/cli': 2.58.6(encoding@0.1.13)
'@sentry/cli': 2.58.6
dotenv: 16.6.1
find-up: 5.0.0
glob: 13.0.6
@@ -17857,10 +17727,10 @@ snapshots:
'@sentry/cli-win32-x64@2.58.6':
optional: true
'@sentry/cli@2.58.6(encoding@0.1.13)':
'@sentry/cli@2.58.6':
dependencies:
https-proxy-agent: 5.0.1
node-fetch: 2.7.0(encoding@0.1.13)
node-fetch: 2.7.0
progress: 2.0.3
proxy-from-env: 1.1.0
which: 2.0.2
@@ -17947,9 +17817,9 @@ snapshots:
'@opentelemetry/semantic-conventions': 1.40.0
'@sentry/core': 10.50.0
'@sentry/webpack-plugin@5.3.0(encoding@0.1.13)(webpack@5.107.1)':
'@sentry/webpack-plugin@5.3.0(webpack@5.107.1)':
dependencies:
'@sentry/bundler-plugin-core': 5.3.0(encoding@0.1.13)
'@sentry/bundler-plugin-core': 5.3.0
webpack: 5.107.1(lightningcss@1.32.0)(postcss@8.5.15)(webpack-cli@7.0.2)
transitivePeerDependencies:
- encoding
@@ -18737,12 +18607,6 @@ snapshots:
'@types/picomatch@4.0.2': {}
'@types/plist@3.0.5':
dependencies:
'@types/node': 18.19.130
xmlbuilder: 15.1.1
optional: true
'@types/png-chunks-extract@1.0.2': {}
'@types/postcss-import@14.0.3':
@@ -18864,9 +18728,6 @@ snapshots:
'@types/unist@3.0.3': {}
'@types/verror@1.10.11':
optional: true
'@types/web-bluetooth@0.0.21': {}
'@types/ws@8.18.1':
@@ -19413,8 +19274,6 @@ snapshots:
'@zxcvbn-ts/language-en@3.0.2': {}
abbrev@3.0.1: {}
abbrev@4.0.0: {}
abort-controller@3.0.0:
@@ -19553,32 +19412,33 @@ snapshots:
normalize-path: 3.0.0
picomatch: 2.3.2
app-builder-bin@5.0.0-alpha.12: {}
app-builder-lib@26.11.1(dmg-builder@26.11.1)(electron-builder-squirrel-windows@26.11.1):
app-builder-lib@26.16.1(dmg-builder@26.16.1)(electron-builder-squirrel-windows@26.16.1):
dependencies:
'@develar/schema-utils': 2.6.5
'@electron/asar': 3.4.1
'@electron/fuses': 1.8.0
'@electron/get': 3.1.0
'@electron/notarize': 2.5.0
'@electron/osx-sign': 1.3.3
'@electron/rebuild': 4.0.3
'@electron/rebuild': 4.2.0
'@electron/universal': 2.0.3
'@malept/flatpak-bundler': 0.4.0
'@noble/hashes': 1.8.0
'@peculiar/webcrypto': 1.7.1
'@types/fs-extra': 9.0.13
ajv: 8.20.0
asn1js: 3.0.10
async-exit-hook: 2.0.1
builder-util: 26.11.1
builder-util-runtime: 9.6.1
builder-util: 26.16.0
builder-util-runtime: 9.7.0
chromium-pickle-js: 0.2.0
ci-info: 4.3.1
debug: 4.4.3
dmg-builder: 26.11.1(electron-builder-squirrel-windows@26.11.1)
dmg-builder: 26.16.1(electron-builder-squirrel-windows@26.16.1)
dotenv: 16.6.1
dotenv-expand: 11.0.7
ejs: 3.1.10
electron-builder-squirrel-windows: 26.11.1(dmg-builder@26.11.1)
electron-publish: 26.11.1
electron-builder-squirrel-windows: 26.16.1(dmg-builder@26.16.1)
electron-publish: 26.16.0
fs-extra: 10.1.0
hosted-git-info: 4.1.0
isbinaryfile: 5.0.7
@@ -19587,6 +19447,7 @@ snapshots:
json5: 2.2.3
lazy-val: 1.0.5
minimatch: 10.2.5
pkijs: 3.4.0
plist: 3.1.0(patch_hash=7a3c35087f0ab2a8efbac9e2899990f07d61e2b3e41d8d962ef0bdb0786e1af7)
proper-lockfile: 4.1.2
resedit: 1.7.2
@@ -19726,9 +19587,6 @@ snapshots:
pvutils: 1.1.5
tslib: 2.8.1
assert-plus@1.0.0:
optional: true
assert@2.1.0:
dependencies:
call-bind: 1.0.8
@@ -19793,6 +19651,8 @@ snapshots:
await-lock@3.0.0(patch_hash=b767a571946a4f8710ac54b1a7bec8a7c1570f9b85d75922392fae20d0578964): {}
aws4@1.13.2: {}
axe-core@4.11.4: {}
axios@1.15.2:
@@ -20174,19 +20034,17 @@ snapshots:
buildcheck@0.0.7:
optional: true
builder-util-runtime@9.6.1:
builder-util-runtime@9.7.0:
dependencies:
debug: 4.4.3
sax: 1.6.0
transitivePeerDependencies:
- supports-color
builder-util@26.11.1:
builder-util@26.16.0:
dependencies:
7zip-bin: 5.2.0
'@types/debug': 4.1.13
app-builder-bin: 5.0.0-alpha.12
builder-util-runtime: 9.6.1
builder-util-runtime: 9.7.0
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.3
@@ -20216,21 +20074,6 @@ snapshots:
bytestreamjs@2.0.1: {}
cacache@19.0.1:
dependencies:
'@npmcli/fs': 4.0.0
fs-minipass: 3.0.3
glob: 10.5.0
lru-cache: 10.4.3
minipass: 7.1.3
minipass-collect: 2.0.1
minipass-flush: 1.0.5
minipass-pipeline: 1.2.4
p-map: 7.0.4
ssri: 12.0.0
tar: 7.5.15
unique-filename: 4.0.0
cacache@20.0.4:
dependencies:
'@npmcli/fs': 5.0.0
@@ -20386,12 +20229,6 @@ snapshots:
cli-spinners@2.6.1: {}
cli-truncate@2.1.0:
dependencies:
slice-ansi: 3.0.0
string-width: 4.2.3
optional: true
cli-truncate@5.2.0:
dependencies:
slice-ansi: 8.0.0
@@ -20564,9 +20401,6 @@ snapshots:
core-js@3.48.0: {}
core-util-is@1.0.2:
optional: true
core-util-is@1.0.3: {}
cose-base@1.0.3:
@@ -20626,11 +20460,6 @@ snapshots:
crc-32: 1.2.2
readable-stream: 4.7.0
crc@3.8.0:
dependencies:
buffer: 5.7.1
optional: true
create-ecdh@4.0.4:
dependencies:
bn.js: 4.12.3
@@ -21165,31 +20994,16 @@ snapshots:
minimatch: 3.1.5
p-limit: 3.1.0
dmg-builder@26.11.1(electron-builder-squirrel-windows@26.11.1):
dmg-builder@26.16.1(electron-builder-squirrel-windows@26.16.1):
dependencies:
app-builder-lib: 26.11.1(dmg-builder@26.11.1)(electron-builder-squirrel-windows@26.11.1)
builder-util: 26.11.1
app-builder-lib: 26.16.1(dmg-builder@26.16.1)(electron-builder-squirrel-windows@26.16.1)
builder-util: 26.16.0
fs-extra: 10.1.0
iconv-lite: 0.6.3
js-yaml: 4.1.1
optionalDependencies:
dmg-license: 1.0.11
transitivePeerDependencies:
- electron-builder-squirrel-windows
- supports-color
dmg-license@1.0.11:
dependencies:
'@types/plist': 3.0.5
'@types/verror': 1.10.11
ajv: 6.15.0
crc: 3.8.0
iconv-corefoundation: 1.1.7
plist: 3.1.0(patch_hash=7a3c35087f0ab2a8efbac9e2899990f07d61e2b3e41d8d962ef0bdb0786e1af7)
smart-buffer: 4.2.0
verror: 1.10.1
optional: true
dns-packet@5.6.1:
dependencies:
'@leichtgewicht/ip-codec': 2.0.5
@@ -21342,23 +21156,23 @@ snapshots:
ejs@5.0.1: {}
electron-builder-squirrel-windows@26.11.1(dmg-builder@26.11.1):
electron-builder-squirrel-windows@26.16.1(dmg-builder@26.16.1):
dependencies:
app-builder-lib: 26.11.1(dmg-builder@26.11.1)(electron-builder-squirrel-windows@26.11.1)
builder-util: 26.11.1
app-builder-lib: 26.16.1(dmg-builder@26.16.1)(electron-builder-squirrel-windows@26.16.1)
builder-util: 26.16.0
electron-winstaller: 5.4.0
transitivePeerDependencies:
- dmg-builder
- supports-color
electron-builder@26.11.1(electron-builder-squirrel-windows@26.11.1):
electron-builder@26.16.1(electron-builder-squirrel-windows@26.16.1):
dependencies:
app-builder-lib: 26.11.1(dmg-builder@26.11.1)(electron-builder-squirrel-windows@26.11.1)
builder-util: 26.11.1
builder-util-runtime: 9.6.1
app-builder-lib: 26.16.1(dmg-builder@26.16.1)(electron-builder-squirrel-windows@26.16.1)
builder-util: 26.16.0
builder-util-runtime: 9.7.0
chalk: 4.1.2
ci-info: 4.4.0
dmg-builder: 26.11.1(electron-builder-squirrel-windows@26.11.1)
dmg-builder: 26.16.1(electron-builder-squirrel-windows@26.16.1)
fs-extra: 10.1.0
lazy-val: 1.0.5
simple-update-notifier: 2.0.0
@@ -21371,11 +21185,12 @@ snapshots:
dependencies:
unzip-crx-3: 0.2.0
electron-publish@26.11.1:
electron-publish@26.16.0:
dependencies:
'@types/fs-extra': 9.0.13
builder-util: 26.11.1
builder-util-runtime: 9.6.1
aws4: 1.13.2
builder-util: 26.16.0
builder-util-runtime: 9.7.0
chalk: 4.1.2
form-data: 4.0.5
fs-extra: 10.1.0
@@ -21450,11 +21265,6 @@ snapshots:
encodeurl@2.0.0: {}
encoding@0.1.13:
dependencies:
iconv-lite: 0.6.3
optional: true
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
@@ -22098,9 +21908,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
extsprintf@1.4.1:
optional: true
fake-indexeddb@6.2.5: {}
fast-content-type-parse@3.0.0: {}
@@ -22775,12 +22582,6 @@ snapshots:
hyperdyperid@1.2.0: {}
iconv-corefoundation@1.1.7:
dependencies:
cli-truncate: 2.1.0
node-addon-api: 1.7.2
optional: true
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@@ -23872,22 +23673,6 @@ snapshots:
dependencies:
semver: 7.8.1
make-fetch-happen@14.0.3:
dependencies:
'@npmcli/agent': 3.0.0
cacache: 19.0.1
http-cache-semantics: 4.2.0
minipass: 7.1.3
minipass-fetch: 4.0.1
minipass-flush: 1.0.5
minipass-pipeline: 1.2.4
negotiator: 1.0.0
proc-log: 5.0.0
promise-retry: 2.0.1
ssri: 12.0.0
transitivePeerDependencies:
- supports-color
make-fetch-happen@15.0.5:
dependencies:
'@gar/promise-retry': 1.0.3
@@ -24176,14 +23961,6 @@ snapshots:
dependencies:
minipass: 7.1.3
minipass-fetch@4.0.1:
dependencies:
minipass: 7.1.3
minipass-sized: 1.0.3
minizlib: 3.1.0
optionalDependencies:
encoding: 0.1.13
minipass-fetch@5.0.2:
dependencies:
minipass: 7.1.3
@@ -24200,10 +23977,6 @@ snapshots:
dependencies:
minipass: 3.3.6
minipass-sized@1.0.3:
dependencies:
minipass: 3.3.6
minipass-sized@2.0.0:
dependencies:
minipass: 7.1.3
@@ -24298,33 +24071,13 @@ snapshots:
dependencies:
semver: 7.8.1
node-addon-api@1.7.2:
optional: true
node-api-version@0.2.1:
dependencies:
semver: 7.8.1
node-fetch@2.7.0(encoding@0.1.13):
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
optionalDependencies:
encoding: 0.1.13
node-gyp@11.5.0:
dependencies:
env-paths: 2.2.1
exponential-backoff: 3.1.3
graceful-fs: 4.2.11
make-fetch-happen: 14.0.3
nopt: 8.1.0
proc-log: 5.0.0
semver: 7.8.1
tar: 7.5.15
tinyglobby: 0.2.16
which: 5.0.0
transitivePeerDependencies:
- supports-color
node-gyp@12.2.0:
dependencies:
@@ -24378,10 +24131,6 @@ snapshots:
non-layered-tidy-tree-layout@2.0.2:
optional: true
nopt@8.1.0:
dependencies:
abbrev: 3.0.1
nopt@9.0.0:
dependencies:
abbrev: 4.0.0
@@ -25601,8 +25350,6 @@ snapshots:
react-is-18: react-is@18.3.1
react-is-19: react-is@19.2.6
proc-log@5.0.0: {}
proc-log@6.1.0: {}
process-nextick-args@2.0.1: {}
@@ -26512,13 +26259,6 @@ snapshots:
slash@5.1.0: {}
slice-ansi@3.0.0:
dependencies:
ansi-styles: 4.3.0
astral-regex: 2.0.0
is-fullwidth-code-point: 3.0.0
optional: true
slice-ansi@4.0.0:
dependencies:
ansi-styles: 4.3.0
@@ -26653,10 +26393,6 @@ snapshots:
ssim.js@3.5.0: {}
ssri@12.0.0:
dependencies:
minipass: 7.1.3
ssri@13.0.1:
dependencies:
minipass: 7.1.3
@@ -27466,14 +27202,6 @@ snapshots:
unicorn-magic@0.4.0: {}
unique-filename@4.0.0:
dependencies:
unique-slug: 5.0.0
unique-slug@5.0.0:
dependencies:
imurmurhash: 0.1.4
unist-util-is@6.0.1:
dependencies:
'@types/unist': 3.0.3
@@ -27662,13 +27390,6 @@ snapshots:
- '@types/react'
- '@types/react-dom'
verror@1.10.1:
dependencies:
assert-plus: 1.0.0
core-util-is: 1.0.2
extsprintf: 1.4.1
optional: true
vfile-message@4.0.3:
dependencies:
'@types/unist': 3.0.3