HealthChat desktop variant
===========================

This directory is the `--cfgdir` / build config for HealthChat desktop builds:

  config.json   Element Web runtime config baked into webapp.asar. Mirrors
                apps/web/config.json (HealthChat homeserver, theme, branding) and
                loads the HealthChat plugin:
                  - "modules": ["/modules/healthchat-module.js"]
                  - "ch.healthchat.elements_base_url": "/webapp/modules/elements"
  build.json    electron-builder variant (appId, productName "HealthChat", protocols).

Why the paths differ from web
-----------------------------
The desktop app serves the webapp from `vector://vector/webapp/`, so root-relative
paths need a `/webapp` prefix:

  - The `modules` array is rewritten automatically by apps/desktop/src/electron-main.ts
    (`/modules/x` -> `/webapp/modules/x`), so it stays as `/modules/healthchat-module.js`.
  - `ch.healthchat.elements_base_url` is NOT auto-rewritten (the elements bundle is loaded
    by a <script> tag the plugin injects, not by the module loader), so it is set explicitly
    to `/webapp/modules/elements` here. On web it is `/modules/elements`.

The plugin artifacts must live inside the asar at `modules/healthchat-module.js` and
`modules/elements/healthchat-elements.js`.

Building locally
----------------
  # 1. Build the element-plugin (from the healthchat-workspace checkout) and stage it:
  (cd ../healthchat-workspace/element-plugin && npm ci && npm run build)
  (cd ../healthchat-workspace/element-plugin/module && npm ci && npm run build)
  mkdir -p plugin-dist/elements
  cp ../healthchat-workspace/element-plugin/module/dist/healthchat-module.js plugin-dist/healthchat-module.js
  cp ../healthchat-workspace/element-plugin/dist/elements/browser/main.js plugin-dist/elements/healthchat-elements.js

  # 2. Build the fork webapp with the plugin injected:
  ./apps/desktop/scripts/build-healthchat-webapp.sh

  # 3. Pack and run:
  cp apps/desktop/element.io/healthchat/config.json apps/desktop/webapp/config.json
  pnpm --dir apps/desktop run asar-webapp
  pnpm --dir apps/desktop start

CI
--
.github/workflows/build-healthchat-desktop.yaml builds the plugin + webapp artifact and
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. 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.)

Branding assets
---------------
App icons live in apps/desktop/build/ and are HealthChat-branded, derived from
apps/web/res/vector-icons/1024.png:
  - icon.png   (Linux, 512x512)
  - icon.ico   (Windows, multi-resolution)
  - icon.icon/ (macOS Apple Icon Composer bundle; Assets/healthchat.png + icon.json)
Regenerate after a logo change with ImageMagick:
  magick apps/web/res/vector-icons/1024.png -resize 512x512 apps/desktop/build/icon.png
  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
them under repo Settings -> Environments with the listed secrets/variables:

  Desktop Apple   (macOS notarisation)
    secrets:  APPLE_ID                 Apple ID email of the signing account
              APPLE_ID_PASSWORD        app-specific password (NOT the Apple ID password)
              APPLE_CSC_LINK           base64 of the Developer ID Application .p12
              APPLE_CSC_KEY_PASSWORD   password for that .p12
    variables: APPLE_TEAM_ID           Apple Developer Team ID

  Windows code-signing (SSL.com eSigner CKA)
    The reusable signs via eSigner CKA and expects ESIGNER_USER_NAME / ESIGNER_USER_PASSWORD /
    ESIGNER_USER_TOTP. The Novaloop org already provides CodeSignTool-style secrets under
    different names (ESIGNER_USERNAME / ESIGNER_PASSWORD / ESIGNER_TOTP_SECRET); the windows job
    in build-healthchat-desktop.yaml maps them. CKA does not use credential_id. These org secrets
    are scoped to "private and internal repositories", so this fork must stay internal/private.
    Set repo or org variable ESIGNER_MODE=prod for a trusted signature (defaults to the untrusted
    sandbox demo cert).

Without these the jobs still build but produce unsigned/untrusted output. Because element-web is
a public-history fork, add environment/branch protection so only release/healthchat + healthchat-v*
runs can reach the signing credentials.

Auto-update + public installer hosting
--------------------------------------
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://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)
  install/  public downloads for the FIRST install (users get these URLs, not the GitHub Release):
              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 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:
  - URL migration: builds <= 1.5.0 poll the legacy feed at
    https://healthchat.ch/packages/desktop/ (docroot /public_html/healthchat.ch/packages/) —
    they only learn the new URL by installing an update delivered over that old feed. The
    deploy workflow therefore dual-publishes to both directories; drop the legacy mirror and
    its verify checks once the installed base is on a build carrying the new update_base_url.
  - 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.
  - Windows auto-update only works for the Squirrel Setup.exe install path, NOT the per-machine
    .msi (the updater needs Squirrel's Update.exe, which the MSI doesn't install). Hand users the
    Setup.exe as the primary Windows installer. macOS: install the .dmg into /Applications.

Open items
----------
  - api_base_url: confirm the production API origin in config.json (currently assumed
    https://healthchat.ch). On desktop this MUST be absolute (vector:// origin can't reach
    a relative /api).
  - App version: the desktop app version comes from apps/desktop/package.json (Windows squirrel
    rewrites any `version` override into a nightly string), NOT the healthchat-v tag. Bump
    apps/desktop/package.json in the release commit if you want the About dialog / installer
    filenames to match the tag.
