diff --git a/.claude/skills/releasing-healthchat/SKILL.md b/.claude/skills/releasing-healthchat/SKILL.md index 137cf693c9..18331deed3 100644 --- a/.claude/skills/releasing-healthchat/SKILL.md +++ b/.claude/skills/releasing-healthchat/SKILL.md @@ -6,13 +6,24 @@ description: Cut a versioned HealthChat release of element-web by tagging releas # Releasing HealthChat (element-web) Releases are driven by git tags of the form `healthchat-v` pushed to -`origin`. The tag triggers `.github/workflows/build-healthchat.yaml`, which -builds the HealthChat Docker image, publishes it to ghcr.io under -`healthchat-web:`, `:stable`, and `:`, and creates a GitHub -Release whose body is the matching section of `HEALTHCHAT_CHANGES.md`. +`origin`. The tag triggers two workflows in parallel: -Publish target: **ghcr.io Docker image + GitHub Release only.** No npm, -no tarball distribution, no desktop/mobile fan-out. +- `.github/workflows/build-healthchat.yaml` (web) — builds the HealthChat Docker + image, publishes it to ghcr.io under `healthchat-web:`, `:stable`, and + `:`, and creates a GitHub Release whose body is the matching section of + `HEALTHCHAT_CHANGES.md`. +- `.github/workflows/build-healthchat-desktop.yaml` (desktop) — builds the Electron + app for Linux (amd64/arm64), macOS (universal, signed+notarised) and Windows + (x64/arm64, signed), and attaches the installers (.dmg/.zip/.exe/.msi/.deb/.tar.gz) + to the **same** GitHub Release. The two workflows create-if-missing then upload, so + they race safely. + +Publish target: **ghcr.io Docker image + GitHub Release (web bundle + desktop +installers).** No npm, no mobile fan-out. + +The desktop app version is taken from `apps/desktop/package.json`, not the tag +(Windows squirrel rewrites any `version` override into a nightly string). Bump it +in the release commit if you want the About dialog / installer filenames to match. ## Preflight checklist @@ -62,6 +73,11 @@ without moving tags. path** — only the tag-push path creates Releases (keeps manual retries from clobbering the release history). +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. + ## Tag format ``` @@ -110,6 +126,11 @@ warning. Prefer fixing the changelog over relying on the fallback. with the correct body. - [ ] The `:stable` tag on ghcr.io now points at the new digest (`docker buildx imagetools inspect ghcr.io//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", *.deb, *.tar.gz). +- [ ] Smoke-check signing: macOS `spctl -a -vvv -t install ` is accepted; + Windows installer shows a valid publisher (no SmartScreen block). ## Required secrets @@ -120,6 +141,22 @@ 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. | +Desktop signing additionally requires two GitHub **environments** (Settings → +Environments). Without them the desktop builds still run but produce +unsigned/untrusted output: + +| Environment | Secrets / variables | +|---|---| +| `Desktop Apple` | secrets `APPLE_ID`, `APPLE_ID_PASSWORD` (app-specific), `APPLE_CSC_LINK` (base64 `.p12`), `APPLE_CSC_KEY_PASSWORD`; variable `APPLE_TEAM_ID` | + +Windows code-signing reuses the org-level SSL.com eSigner secrets shared with the +other Novaloop apps (`ESIGNER_USERNAME`, `ESIGNER_PASSWORD`, `ESIGNER_TOTP_SECRET`); +the windows job in `build-healthchat-desktop.yaml` maps them to the CKA names the +reusable expects (no `credential_id` needed). Set variable `ESIGNER_MODE=prod` for a +trusted signature. These org secrets are scoped to private/internal repos, so the +fork must stay **internal** (not public). Add branch/environment protection so only +`release/healthchat` + `healthchat-v*` runs can reach the signing credentials. + ## Rollback - **Bad image, notes fine** → fix the cause, commit, tag `healthchat-v-hotfix1`. @@ -135,6 +172,19 @@ These files are HealthChat-only and will conflict cleanly when rebasing during rebase): - `.github/workflows/build-healthchat.yaml` +- `.github/workflows/build-healthchat-desktop.yaml` - `HEALTHCHAT_CHANGES.md` - `.healthchat-workspace-version` - `.claude/skills/releasing-healthchat/SKILL.md` +- `apps/desktop/build/icon.png`, `apps/desktop/build/icon.ico`, + `apps/desktop/build/icon.icon/` (HealthChat icons) + +The four shared desktop reusables carry small HealthChat additions (a `repository` +input + product-name-agnostic asserts). These should merge cleanly, but if they +conflict on rebase, re-apply the HealthChat changes rather than blindly taking +either side: + +- `.github/workflows/build_desktop_prepare.yaml` +- `.github/workflows/build_desktop_linux.yaml` +- `.github/workflows/build_desktop_macos.yaml` +- `.github/workflows/build_desktop_windows.yaml` diff --git a/.github/workflows/build-healthchat-desktop.yaml b/.github/workflows/build-healthchat-desktop.yaml index b9a14cc1f9..efb6a56419 100644 --- a/.github/workflows/build-healthchat-desktop.yaml +++ b/.github/workflows/build-healthchat-desktop.yaml @@ -1,33 +1,34 @@ name: Build HealthChat Desktop -# Builds the HealthChat desktop (Electron) app: the fork's element-web webapp with -# the HealthChat plugin (healthchat-module.js + healthchat-elements.js) injected, -# packaged via the existing element-desktop build pipeline. +# Builds the HealthChat desktop (Electron) app for Linux, macOS and Windows: the fork's +# element-web webapp with the HealthChat plugin (healthchat-module.js + healthchat-elements.js) +# injected, packaged via the element-desktop build pipeline and published to the GitHub Release. # -# This mirrors build-healthchat.yaml (web). It first builds the element-plugin from -# healthchat-workspace, builds the fork webapp with the plugin injected, then feeds it -# into build_desktop_prepare.yaml via its `webapp-artifact` input and packages it with -# build_desktop_linux.yaml. +# This is the desktop counterpart of build-healthchat.yaml (web). On a `healthchat-v*` tag it +# runs alongside the web build; it first builds the element-plugin from healthchat-workspace, +# builds the fork webapp with the plugin injected, then feeds it into build_desktop_prepare.yaml +# (via `webapp-artifact`) and the per-platform build_desktop_{linux,macos,windows}.yaml reusables. # -# IMPORTANT CAVEATS (see apps/desktop/element.io/healthchat/README): -# - build_desktop_prepare.yaml / build_desktop_linux.yaml currently `actions/checkout` -# `element-hq/element-web` (upstream). The HealthChat webapp + branding come from the -# uploaded artifact + variant.json, but the Electron shell (electron-main, icons) is -# upstream Element's. To ship a fully fork-branded shell, point those reusable -# workflows at this fork (change their `repository:` / add a `ref` input). -# - Plugin loading depends on element-main.ts rewriting `/modules/*` -> `/webapp/modules/*`. -# Verify that rewrite exists in whichever desktop checkout is used. -# - macOS/Windows builds need Element's signing/notarization secrets and are intentionally -# omitted here; add build_desktop_macos.yaml / build_desktop_windows.yaml once those are -# available. -# - Confirm `ch.healthchat.api_base_url` in apps/desktop/element.io/healthchat/config.json -# points at the real production API origin (assumed https://healthchat.ch). +# Notes: +# - 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 +# apps/desktop/element.io/healthchat/{build.json,config.json} (-> variant.json). +# - macOS notarisation and Windows code-signing require the `Desktop Apple` / `Desktop eSigner` +# GitHub environments (secrets/vars) to be configured. Without them the builds still run but +# produce unsigned/untrusted output. See apps/desktop/element.io/healthchat/README. +# - The desktop app version comes from apps/desktop/package.json (electron-builder), NOT the tag +# (Windows squirrel rewrites any `version` override into a nightly string). Bump +# apps/desktop/package.json in the release commit if you want it to match the tag. on: + push: + tags: + - "healthchat-v*" workflow_dispatch: inputs: version: - description: "Version (e.g. 1.4.0)." + description: "Version (e.g. 1.4.0). Used for the web bundle version; the desktop app version comes from apps/desktop/package.json." required: true type: string workspace_ref: @@ -38,7 +39,7 @@ on: permissions: {} concurrency: - group: build-healthchat-desktop-${{ github.event.inputs.version }} + group: build-healthchat-desktop-${{ github.event.inputs.version || github.ref_name }} cancel-in-progress: false jobs: @@ -52,6 +53,26 @@ jobs: with: persist-credentials: false + - name: Resolve version + id: version + env: + INPUT_VERSION: ${{ github.event.inputs.version }} + run: | + set -euo pipefail + if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then + VERSION="${GITHUB_REF_NAME#healthchat-v}" + elif [[ -n "${INPUT_VERSION:-}" ]]; then + VERSION="${INPUT_VERSION}" + else + echo "::error::No version available (not a tag push and no version input)" + exit 1 + fi + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then + echo "::error::Invalid version '$VERSION'" + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + - name: Resolve workspace ref id: workspace env: @@ -105,7 +126,7 @@ jobs: - name: Build webapp with plugin injected env: - VERSION: ${{ github.event.inputs.version }} + VERSION: ${{ steps.version.outputs.version }} run: ./apps/desktop/scripts/build-healthchat-webapp.sh - uses: actions/upload-artifact@v4 @@ -121,8 +142,13 @@ jobs: contents: read uses: ./.github/workflows/build_desktop_prepare.yaml with: + repository: ${{ github.repository }} + ref: ${{ github.sha }} config: element.io/healthchat webapp-artifact: healthchat-webapp + # 'develop' skips the upstream "[Release] Fetch release" / Debian changelog step, + # which would look for a non-existent v release in this fork. + version: develop nightly: false deploy: false @@ -134,7 +160,86 @@ jobs: arch: [amd64, arm64] uses: ./.github/workflows/build_desktop_linux.yaml with: + repository: ${{ github.repository }} + ref: ${{ github.sha }} arch: ${{ matrix.arch }} sqlcipher: static - version: ${{ github.event.inputs.version }} test: false + + macos: + name: macOS + needs: prepare + uses: ./.github/workflows/build_desktop_macos.yaml + secrets: inherit # zizmor: ignore[secrets-inherit] + with: + repository: ${{ github.repository }} + ref: ${{ github.sha }} + sign: true + test: false + targets: "dmg zip" + + windows: + name: Windows ${{ matrix.arch }} + needs: prepare + strategy: + matrix: + arch: [x64, arm64] + uses: ./.github/workflows/build_desktop_windows.yaml + # element-desktop's Windows reusable signs via SSL.com eSigner CKA, whose secret names + # differ from the org-level CodeSignTool secrets shared with the other Novaloop apps + # (CKA needs no credential_id). Map them here. A trusted signature also requires the + # repo/org variable ESIGNER_MODE=prod (otherwise the reusable falls back to the untrusted + # sandbox demo cert). + secrets: + ESIGNER_USER_NAME: ${{ secrets.ESIGNER_USERNAME }} + ESIGNER_USER_PASSWORD: ${{ secrets.ESIGNER_PASSWORD }} + ESIGNER_USER_TOTP: ${{ secrets.ESIGNER_TOTP_SECRET }} + with: + repository: ${{ github.repository }} + ref: ${{ github.sha }} + arch: ${{ matrix.arch }} + sign: true + test: false + targets: "squirrel msi" + + release: + name: Publish to GitHub Release + needs: [linux, 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: ubuntu-latest + permissions: + contents: write + steps: + - 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/ \; + # Linux: portable tarball + Debian package + find dist-artifacts -type f \( -iname '*.tar.gz' -o -iname '*.deb' \) -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 + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + 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. + 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 diff --git a/.github/workflows/build_desktop_linux.yaml b/.github/workflows/build_desktop_linux.yaml index ae050843f8..2fe4244a57 100644 --- a/.github/workflows/build_desktop_linux.yaml +++ b/.github/workflows/build_desktop_linux.yaml @@ -4,6 +4,10 @@ on: workflow_call: inputs: + repository: + type: string + required: false + description: "The repository to checkout the desktop source from, defaults to element-hq/element-web" ref: type: string required: false @@ -93,7 +97,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - repository: element-hq/element-web + repository: ${{ inputs.repository || 'element-hq/element-web' }} ref: ${{ inputs.ref }} persist-credentials: false @@ -212,7 +216,7 @@ jobs: assert_contains_string "$LIBS" "libsqlcipher.so.0" fi - ./scripts/glibc-check.sh dist/linux-*unpacked/element-desktop* + ./scripts/glibc-check.sh dist/linux-*unpacked/*-desktop* env: ARCH: ${{ steps.config.outputs.arch }} @@ -230,9 +234,9 @@ jobs: if: contains(inputs.targets, 'deb') working-directory: apps/desktop run: | - test -f ./dist/element-desktop*$ARCH.deb + test -f ./dist/*-desktop*$ARCH.deb - DEB_LISTING=$(dpkg-deb --fsys-tarfile ./dist/element-desktop*.deb | tar -tv) + DEB_LISTING=$(dpkg-deb --fsys-tarfile ./dist/*-desktop*.deb | tar -tv) echo "deb listing: " echo "$DEB_LISTING" ! echo "$DEB_LISTING" | grep '^h' @@ -243,9 +247,9 @@ jobs: if: contains(inputs.targets, 'tar.gz') working-directory: apps/desktop run: | - test -f ./dist/element-desktop*.tar.gz + test -f ./dist/*-desktop*.tar.gz - TAR_GZ_LISTING=$(tar -tvf ./dist/element-desktop*.tar.gz) + TAR_GZ_LISTING=$(tar -tvf ./dist/*-desktop*.tar.gz) echo "tar.gz listing: " echo "$TAR_GZ_LISTING" ! echo "$TAR_GZ_LISTING" | grep '^h' @@ -259,7 +263,7 @@ jobs: project: linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }} artifact: ${{ inputs.artifact-prefix }}linux-${{ inputs.arch }}-sqlcipher-${{ inputs.sqlcipher }} runs-on: ${{ inputs.runs-on || (inputs.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04') }} - executable: /opt/Element*/element-desktop* + executable: /opt/*/*-desktop* prepare_cmd: | sudo apt-get -qq update sudo apt install ./dist/*.deb diff --git a/.github/workflows/build_desktop_macos.yaml b/.github/workflows/build_desktop_macos.yaml index 134aba6dde..2e17834a4a 100644 --- a/.github/workflows/build_desktop_macos.yaml +++ b/.github/workflows/build_desktop_macos.yaml @@ -13,6 +13,10 @@ on: APPLE_CSC_LINK: required: false inputs: + repository: + type: string + required: false + description: "The repository to checkout the desktop source from, defaults to element-hq/element-web" ref: type: string required: false @@ -79,7 +83,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - repository: element-hq/element-web + repository: ${{ inputs.repository || 'element-hq/element-web' }} ref: ${{ inputs.ref }} persist-credentials: false @@ -206,13 +210,13 @@ jobs: if: contains(inputs.targets, 'zip') working-directory: apps/desktop run: | - test -f ./dist/Element*-mac.zip + test -f ./dist/*-mac.zip - name: Assert dmg is present if: contains(inputs.targets, 'dmg') working-directory: apps/desktop run: | - test -f ./dist/Element*.dmg + test -f ./dist/*.dmg test: name: Test macOS Universal @@ -223,12 +227,12 @@ jobs: project: macos artifact: ${{ inputs.artifact-prefix }}macos runs-on: macos-14 - executable: /Users/runner/Applications/Element*.app/Contents/MacOS/Element* + executable: /Users/runner/Applications/*.app/Contents/MacOS/* # We need to mount the DMG and copy the app to the Applications folder as a mounted DMG is # read-only and thus would not allow us to override the fuses as is required for Playwright. prepare_cmd: | hdiutil attach ./dist/*.dmg -mountpoint /Volumes/Element && - rsync -a /Volumes/Element/Element*.app ~/Applications/ && + rsync -a /Volumes/Element/*.app ~/Applications/ && hdiutil detach /Volumes/Element blob_report: ${{ inputs.blob_report }} args: ${{ inputs.test-args }} diff --git a/.github/workflows/build_desktop_prepare.yaml b/.github/workflows/build_desktop_prepare.yaml index e01065945e..57c278504c 100644 --- a/.github/workflows/build_desktop_prepare.yaml +++ b/.github/workflows/build_desktop_prepare.yaml @@ -2,6 +2,14 @@ on: workflow_call: inputs: + repository: + type: string + required: false + description: "The repository to checkout the desktop source from, defaults to element-hq/element-web" + ref: + type: string + required: false + description: "The git ref to checkout, defaults to the default branch" config: type: string required: true @@ -56,7 +64,8 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - repository: element-hq/element-web + repository: ${{ inputs.repository || 'element-hq/element-web' }} + ref: ${{ inputs.ref }} - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 diff --git a/.github/workflows/build_desktop_windows.yaml b/.github/workflows/build_desktop_windows.yaml index 7c588d5f56..42789f7b6c 100644 --- a/.github/workflows/build_desktop_windows.yaml +++ b/.github/workflows/build_desktop_windows.yaml @@ -18,6 +18,10 @@ on: ESIGNER_USER_TOTP: required: false inputs: + repository: + type: string + required: false + description: "The repository to checkout the desktop source from, defaults to element-hq/element-web" ref: type: string required: false @@ -116,7 +120,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - repository: element-hq/element-web + repository: ${{ inputs.repository || 'element-hq/element-web' }} ref: ${{ inputs.ref }} persist-credentials: false @@ -290,21 +294,21 @@ jobs: - name: Assert executable is present working-directory: apps/desktop run: | - Test-Path './dist/win-*unpacked/Element*.exe' + Test-Path './dist/win-*unpacked/*.exe' - name: Assert all Squirrel files are present if: contains(inputs.targets, 'squirrel') working-directory: apps/desktop run: | - Test-Path './dist/squirrel-windows*/Element Setup*.exe' - Test-Path './dist/squirrel-windows*/element-desktop-*-full.nupkg' + Test-Path './dist/squirrel-windows*/* Setup*.exe' + Test-Path './dist/squirrel-windows*/*-full.nupkg' Test-Path './dist/squirrel-windows*/RELEASES' - name: Assert MSI is present if: contains(inputs.targets, 'msi') working-directory: apps/desktop run: | - Test-Path './dist/Element*.msi' + Test-Path './dist/*.msi' test: name: Test Windows ${{ inputs.arch }} @@ -315,6 +319,6 @@ jobs: project: win-${{ inputs.arch }} artifact: ${{ inputs.artifact-prefix }}win-${{ inputs.arch }} runs-on: ${{ inputs.test-runs-on || (inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2022') }} - executable: ./dist/win*-unpacked/Element*.exe + executable: ./dist/win*-unpacked/*.exe blob_report: ${{ inputs.blob_report }} args: ${{ inputs.test-args }} diff --git a/apps/desktop/build/icon.ico b/apps/desktop/build/icon.ico index a6349a3282..520f643272 100644 Binary files a/apps/desktop/build/icon.ico and b/apps/desktop/build/icon.ico differ diff --git a/apps/desktop/build/icon.icon/Assets/element.png b/apps/desktop/build/icon.icon/Assets/element.png deleted file mode 100644 index cfbd8c600a..0000000000 Binary files a/apps/desktop/build/icon.icon/Assets/element.png and /dev/null differ diff --git a/apps/desktop/build/icon.icon/Assets/healthchat.png b/apps/desktop/build/icon.icon/Assets/healthchat.png new file mode 100644 index 0000000000..7508ec245b Binary files /dev/null and b/apps/desktop/build/icon.icon/Assets/healthchat.png differ diff --git a/apps/desktop/build/icon.icon/icon.json b/apps/desktop/build/icon.icon/icon.json index 99cc571fc9..51267110e1 100644 --- a/apps/desktop/build/icon.icon/icon.json +++ b/apps/desktop/build/icon.icon/icon.json @@ -5,8 +5,8 @@ "layers": [ { "glass": false, - "image-name": "element.png", - "name": "element" + "image-name": "healthchat.png", + "name": "healthchat" } ], "name": "Figma", diff --git a/apps/desktop/build/icon.png b/apps/desktop/build/icon.png index ea5fa84819..abf26985c0 100644 Binary files a/apps/desktop/build/icon.png and b/apps/desktop/build/icon.png differ diff --git a/apps/desktop/element.io/healthchat/README b/apps/desktop/element.io/healthchat/README index 189bb1d8f7..160e28b76d 100644 --- a/apps/desktop/element.io/healthchat/README +++ b/apps/desktop/element.io/healthchat/README @@ -45,18 +45,57 @@ 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 build_desktop_linux.yaml. +`webapp-artifact: healthchat-webapp`) and the per-platform build_desktop_{linux,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 / .deb / .tar.gz to the GitHub Release. -Open items before a production release --------------------------------------- +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 + +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. + +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). - - Fork vs upstream shell: build_desktop_prepare.yaml / build_desktop_linux.yaml check out - element-hq/element-web (upstream). The webapp + variant are HealthChat's, but the Electron - shell (electron-main, icons) is upstream. Point those workflows at this fork to fully brand - the shell, and to guarantee the `/modules` -> `/webapp/modules` rewrite is present. - - macOS/Windows: need signing/notarization secrets; add the macos/windows reusable workflows - once configured. - - Deploy target: the upstream deploy job pushes to packages.element.io / Cloudflare R2 — wire - up a HealthChat distribution target instead. + - 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. + - Auto-update: not wired up. The apps install from the GitHub Release but do not self-update. + Adding it needs a hosted update feed (releases.json / RELEASES) on a stable URL.