diff --git a/.claude/skills/releasing-healthchat/SKILL.md b/.claude/skills/releasing-healthchat/SKILL.md new file mode 100644 index 0000000000..137cf693c9 --- /dev/null +++ b/.claude/skills/releasing-healthchat/SKILL.md @@ -0,0 +1,140 @@ +--- +name: releasing-healthchat +description: Cut a versioned HealthChat release of element-web by tagging release/healthchat. Use when the user asks to release, publish, tag, or cut a new HealthChat version, or asks about the release/deployment process for HealthChat element-web. +--- + +# 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`. + +Publish target: **ghcr.io Docker image + GitHub Release only.** No npm, +no tarball distribution, no desktop/mobile fan-out. + +## Preflight checklist + +Verify each item before tagging. If any fail, fix and commit first. + +- [ ] On branch `release/healthchat` with a clean working tree (`git status`). +- [ ] `HEALTHCHAT_CHANGES.md` has a section `## Changes in ()` + at the top of the file. The pipeline's awk extractor matches this exact + heading format. +- [ ] `.healthchat-workspace-version` points at the healthchat-workspace tag + or ref that should ship with this release. Bump it in the same commit + as the changelog entry if needed. +- [ ] `package.json` version can optionally be bumped to match (cosmetic — + the git tag is the source of truth for the build). +- [ ] No uncommitted changes to `.github/workflows/build-healthchat.yaml`, + `Dockerfile`, or `docker/` that haven't been manually sanity-checked. + +## Cutting the release (normal path) + +```bash +# from release/healthchat, fully committed: +git tag healthchat-v.. +git push origin healthchat-v.. +``` + +Examples: + +- `git tag healthchat-v1.0.0` +- `git tag healthchat-v1.4.0-rc1` +- `git tag healthchat-v1.4.0-hotfix1` + +The workflow triggers automatically. Watch it via `gh run watch` or GitHub +Actions UI. + +## Manual / retry path (workflow_dispatch) + +Use when retrying a failed run, testing the pipeline, or rebuilding an image +without moving tags. + +1. GitHub → Actions → "Release HealthChat" → **Run workflow**. +2. Fill in: + - **version** (required): e.g. `1.4.0`. Same regex validation as tag path. + - **workspace_ref** (optional): overrides `.healthchat-workspace-version`. + - **release_notes_override** (optional): replaces the `HEALTHCHAT_CHANGES.md` + 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). + +## Tag format + +``` +healthchat-v..[-] +``` + +Suffix conventions (informational; regex accepts any +`[0-9A-Za-z.-]+`): + +- `-rc1`, `-rc2` — release candidates +- `-hotfix1` — post-release fixes on an existing minor +- `-dryrun1`, `-renew-test` — one-off pipeline tests + +The `healthchat-` prefix keeps these tags distinct from upstream +element-hq `v` tags if they are ever fetched into the repo. + +Regex used for validation (must match): +`^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$` + +## Release notes + +`HEALTHCHAT_CHANGES.md` is the single source. The workflow extracts the +section matching the version being released. Format: + +```markdown +## Changes in 1.4.0 (2026-04-21) + +Short summary line (optional). + +🙌 Improvements + +- Bullet one. +- Bullet two. +``` + +If no matching section exists, the pipeline falls back to +`HealthChat — automated build from .` and emits a workflow +warning. Prefer fixing the changelog over relying on the fallback. + +## After the release + +- [ ] Workflow run is green in GitHub Actions. +- [ ] `docker pull ghcr.io//healthchat-web:` succeeds. +- [ ] GitHub Release exists at + `https://github.com///releases/tag/healthchat-v` + 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`). + +## Required secrets + +Configured in repo Settings → Secrets and variables → Actions: + +| Secret | Purpose | +|---|---| +| `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. | + +## Rollback + +- **Bad image, notes fine** → fix the cause, commit, tag `healthchat-v-hotfix1`. +- **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. +- **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. + +## Known branch divergence on rebase + +These files are HealthChat-only and will conflict cleanly when rebasing +`release/healthchat` onto upstream. Always keep the HealthChat side (`--ours` +during rebase): + +- `.github/workflows/build-healthchat.yaml` +- `HEALTHCHAT_CHANGES.md` +- `.healthchat-workspace-version` +- `.claude/skills/releasing-healthchat/SKILL.md` diff --git a/.github/workflows/build-healthchat.yaml b/.github/workflows/build-healthchat.yaml index 4754d1061f..6a2a2b060e 100644 --- a/.github/workflows/build-healthchat.yaml +++ b/.github/workflows/build-healthchat.yaml @@ -1,42 +1,133 @@ -name: build-healthchat-app +name: Release HealthChat + +# Tag-driven release pipeline for HealthChat builds of element-web. +# Mirrors ../element-ios/.github/workflows/release-healthchat.yml. +# Runbook: .claude/skills/releasing-healthchat/SKILL.md -# run on branch release/healthchat on: push: - branches: - - release/healthchat + tags: + - "healthchat-v*" + workflow_dispatch: + inputs: + version: + description: "Version (e.g. 1.4.0). Required when running manually." + required: true + type: string + workspace_ref: + description: "Override healthchat-workspace ref (defaults to .healthchat-workspace-version)." + required: false + type: string + release_notes_override: + description: "Optional release notes; falls back to HEALTHCHAT_CHANGES.md section for the given version." + required: false + type: string + +permissions: + contents: write # create GitHub Release + packages: write # push Docker image + id-token: write # attestations + attestations: write + +concurrency: + group: release-healthchat-${{ github.event.inputs.version || github.ref_name }} + cancel-in-progress: false jobs: - build-healthchat-app: + release: + name: Build & publish HealthChat image runs-on: ubuntu-latest - permissions: - contents: read # This is required to read the repo content - packages: write # This is required to push the Docker image - id-token: write # Required to get the certificate for the attestation - attestations: write # Required to push the attestation env: DOCKER_REGISTRY: "ghcr.io" DOCKER_IMAGE: ${{ format('{0}/healthchat-web', github.repository) }} - DOCKER_IMAGE_TAG: "latest" DOCKER_PUSH: true DOCKER_SIGNING: false FORCE_COLOR: 1 - HEALTHCHAT_WORKSPACE_REF: v0.5.0-rc.4 steps: - uses: actions/checkout@v4 with: - fetch-depth: 0 # needed for docker-package to be able to calculate the version + fetch-depth: 0 + + - name: Resolve version + id: version + run: | + set -euo pipefail + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION="${GITHUB_REF_NAME#healthchat-v}" + fi + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then + echo "::error::Invalid version '$VERSION' — expected e.g. 1.4.0 or 1.4.0-rc1" + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Resolved version: $VERSION" + + - name: Resolve workspace ref + id: workspace + env: + OVERRIDE: ${{ github.event.inputs.workspace_ref }} + run: | + set -euo pipefail + if [[ -n "$OVERRIDE" ]]; then + REF="$OVERRIDE" + echo "Using workspace ref from workflow input: $REF" + elif [[ -f .healthchat-workspace-version ]]; then + REF=$(head -n1 .healthchat-workspace-version | tr -d '[:space:]') + if [[ -z "$REF" ]]; then + echo "::error::.healthchat-workspace-version is empty" + exit 1 + fi + echo "Using workspace ref from .healthchat-workspace-version: $REF" + else + echo "::error::.healthchat-workspace-version missing and no workspace_ref override provided" + exit 1 + fi + echo "ref=$REF" >> "$GITHUB_OUTPUT" + + - name: Extract release notes + id: notes + env: + VERSION: ${{ steps.version.outputs.version }} + OVERRIDE: ${{ github.event.inputs.release_notes_override }} + run: | + set -euo pipefail + if [[ -n "$OVERRIDE" ]]; then + NOTES="$OVERRIDE" + echo "Using release notes from workflow input" + else + NOTES=$(awk -v ver="$VERSION" ' + /^## Changes in / { + if (found) { exit } + if ($0 ~ "## Changes in " ver "( |$|\\()") { found = 1; next } + } + found { print } + ' HEALTHCHAT_CHANGES.md | sed '/^[[:space:]]*$/{N;/^\n$/D;}') + if [[ -z "$NOTES" ]]; then + NOTES="HealthChat ${VERSION} — automated build from ${GITHUB_REF_NAME}." + echo "::warning::No HEALTHCHAT_CHANGES.md section found for $VERSION — using fallback" + fi + fi + { + echo 'notes<> "$GITHUB_OUTPUT" + - name: Checkout healthchat-workspace uses: actions/checkout@v4 with: repository: Novaloop-AG/healthchat-workspace path: healthchat-workspace token: ${{ secrets.HEALTHCHAT_WORKSPACE_TOKEN }} - ref: ${{ env.HEALTHCHAT_WORKSPACE_REF }} + ref: ${{ steps.workspace.outputs.ref }} + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 + - name: Build element-plugin run: | cd healthchat-workspace/element-plugin @@ -45,27 +136,33 @@ jobs: cd module npm ci npm run build + - name: Stage plugin artifacts for Docker run: | 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 + - name: Log in to the Github Container registry uses: docker/login-action@v3 with: registry: ${{ env.DOCKER_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker id: metadata uses: docker/metadata-action@v5 with: images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }} tags: | - type=raw,value=${{ env.DOCKER_IMAGE_TAG }} + type=raw,value=${{ steps.version.outputs.version }} + type=raw,value=stable type=raw,value=${{ github.sha }} + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Build and push Docker images id: push uses: docker/build-push-action@v6 @@ -75,8 +172,6 @@ jobs: push: ${{ env.DOCKER_PUSH }} tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} - #cache-from: type=gha - #cache-to: type=gha,mode=max - name: Generate artifact attestation if: ${{ env.DOCKER_SIGNING == 'true' }} @@ -85,3 +180,11 @@ jobs: subject-name: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: ${{ env.DOCKER_PUSH }} + + - name: Create GitHub Release + if: github.event_name == 'push' + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: HealthChat ${{ steps.version.outputs.version }} + body: ${{ steps.notes.outputs.notes }} diff --git a/.healthchat-workspace-version b/.healthchat-workspace-version new file mode 100644 index 0000000000..242dd4f478 --- /dev/null +++ b/.healthchat-workspace-version @@ -0,0 +1 @@ +v0.5.0-rc.4 diff --git a/CLAUDE.md b/CLAUDE.md index be00c79c28..a9ed8c0ffd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,4 +111,7 @@ yarn link && yarn install # In element-web directory yarn link matrix-js-sdk && yarn install && yarn start -``` \ No newline at end of file +``` + +### Releasing HealthChat +Releases are cut by pushing a `healthchat-v` tag against `release/healthchat`, which triggers `.github/workflows/build-healthchat.yaml`. Release notes come from `HEALTHCHAT_CHANGES.md`; the plugin ref is pinned in `.healthchat-workspace-version`. Full runbook: `releasing-healthchat` skill. \ No newline at end of file diff --git a/HEALTHCHAT_CHANGES.md b/HEALTHCHAT_CHANGES.md new file mode 100644 index 0000000000..67f7aabb3c --- /dev/null +++ b/HEALTHCHAT_CHANGES.md @@ -0,0 +1,16 @@ +# HealthChat Changelog + +Release notes for HealthChat builds of element-web. The release pipeline +(`.github/workflows/build-healthchat.yaml`) extracts the matching `## Changes +in ` 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.0.0 (2026-04-21) + +First tagged HealthChat release of element-web. + +🙌 Improvements + +- Initial HealthChat branding, theming, and healthchat-workspace module integration.