Compare commits

...

2 Commits

Author SHA1 Message Date
Khalid Abdi 46c32b432c chore(release): v0.2.5
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:54:47 +03:00
Khalid Abdi 5bbfe551fc release workflow: publish multi-arch images; settings: confirm language switch
- release.yml: add QEMU + platforms: linux/amd64,linux/arm64 to both
  build-push steps so the published images work on Apple Silicon (fixes
  'no matching manifest for linux/arm64/v8' on docker compose pull).
- Settings -> Profile language picker is now a select that opens a
  confirmation dialog before applying, instead of switching instantly.
- CLAUDE.md: require a dated docs changelog entry for every release.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:54:47 +03:00
9 changed files with 89 additions and 20 deletions
+7
View File
@@ -35,6 +35,11 @@ jobs:
id: meta
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
# QEMU lets the amd64 runner emulate arm64 so the images below build for
# both platforms (Intel + Apple Silicon self-hosters).
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
@@ -49,6 +54,7 @@ jobs:
with:
context: ./backend
push: true
platforms: linux/amd64,linux/arm64
tags: |
${{ env.REGISTRY_NAMESPACE }}/temetro-backend:${{ steps.meta.outputs.version }}
${{ env.REGISTRY_NAMESPACE }}/temetro-backend:latest
@@ -58,6 +64,7 @@ jobs:
with:
context: ./frontend
push: true
platforms: linux/amd64,linux/arm64
tags: |
${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:${{ steps.meta.outputs.version }}
${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:latest
+14
View File
@@ -7,6 +7,20 @@ for how releases are cut and published.
## [Unreleased]
## [0.2.5] — 2026-07-01
### Fixed
- **Multi-arch Docker images** — the `release` workflow now builds and publishes
`khalidxv/temetro-backend` and `khalidxv/temetro-frontend` for both
`linux/amd64` and `linux/arm64`. Previously the images were amd64-only, so
`docker compose pull` on Apple Silicon failed with *no matching manifest for
linux/arm64/v8* and fell back to building from source.
### Changed
- **Language switcher** in Settings → Profile is now a **select** that asks for
confirmation before switching the interface language, instead of applying the
change instantly on a button tap.
## [0.2.4] — 2026-06-29
### Added
+5
View File
@@ -80,6 +80,11 @@ accurate (e.g. a new backend route needs an `content/docs/api/*.mdx` entry; a UI
in the matching guide; status changes belong in the roadmap). Commit docs changes inside that
repo, separately from this one.
**Every release must also get a dated entry in the docs changelog**
(`content/docs/changelog.mdx`, newest first) — not just the monorepo `CHANGELOG.md`. When you cut a
version (see "Always release after pushing"), add a matching, user-facing section to that page in the
same session so `../temetro/docs` never falls behind the shipped version.
## Running the stack
From `backend/`: ensure a `.env` exists (`cp .env.example .env`, then set `BETTER_AUTH_SECRET` via
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "temetro-backend",
"version": "0.2.4",
"version": "0.2.5",
"private": true,
"type": "module",
"description": "temetro backend — Express + Postgres API with Better Auth (email/password, organizations) and org-scoped patient records.",
@@ -6,7 +6,15 @@ import { useTranslation } from "react-i18next";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import { Input } from "@/components/ui/input";
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { DeleteAccountDialog } from "@/components/settings/delete-account-dialog";
import {
CopyField,
@@ -68,6 +76,10 @@ export function ProfilePanel() {
const [baselineName, setBaselineName] = useState("");
const [saving, setSaving] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
// A language picked in the Select but not yet applied — its presence opens the
// confirmation dialog. The Select stays bound to `activeLang`, so cancelling
// (clearing this) automatically reverts the shown selection.
const [pendingLang, setPendingLang] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
@@ -222,21 +234,23 @@ export function ProfilePanel() {
>
<SettingsCard className="space-y-1.5 p-5">
<FieldLabel>{t("settings.profile.language.label")}</FieldLabel>
<div className="flex flex-wrap gap-2">
{supportedLanguages.map((lng) => (
<Button
aria-pressed={activeLang === lng}
className="rounded-3xl"
key={lng}
onClick={() => void i18n.changeLanguage(lng)}
size="sm"
type="button"
variant={activeLang === lng ? "default" : "outline"}
>
{t(`settings.profile.language.${lng}`)}
</Button>
))}
</div>
<Select
onValueChange={(value) => {
if (value !== activeLang) setPendingLang(value);
}}
value={activeLang}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{supportedLanguages.map((lng) => (
<SelectItem key={lng} value={lng}>
{t(`settings.profile.language.${lng}`)}
</SelectItem>
))}
</SelectPopup>
</Select>
</SettingsCard>
</SettingsSection>
@@ -316,6 +330,27 @@ export function ProfilePanel() {
<DeleteAccountDialog onOpenChange={setDeleteOpen} open={deleteOpen} />
<ConfirmDialog
cancelLabel={t("settings.profile.language.cancel")}
confirmLabel={t("settings.profile.language.confirmCta")}
description={
pendingLang
? t("settings.profile.language.confirmBody", {
language: t(`settings.profile.language.${pendingLang}`),
})
: undefined
}
onConfirm={() => {
if (pendingLang) void i18n.changeLanguage(pendingLang);
}}
onOpenChange={(open) => {
if (!open) setPendingLang(null);
}}
open={pendingLang !== null}
title={t("settings.profile.language.confirmTitle")}
variant="default"
/>
{dirty ? (
<div className="sticky bottom-4 z-10">
<div className="flex items-center justify-between gap-4 rounded-2xl border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur">
@@ -1689,7 +1689,11 @@
"description": "The language temetro's interface is shown in on this device.",
"label": "Display language",
"en": "English",
"fr": "Français"
"fr": "Français",
"confirmTitle": "Change display language?",
"confirmBody": "Switch the interface to {{language}}? The app will reload its text in the new language.",
"confirmCta": "Change language",
"cancel": "Cancel"
},
"patientNotifications": "Patient notifications",
"patientNotificationsDescription": "Emails sent to patients about their records, results, and pending approvals",
@@ -1689,7 +1689,11 @@
"description": "La langue dans laquelle l'interface de temetro s'affiche sur cet appareil.",
"label": "Langue d'affichage",
"en": "English",
"fr": "Français"
"fr": "Français",
"confirmTitle": "Changer la langue d'affichage ?",
"confirmBody": "Basculer l'interface en {{language}} ? L'application rechargera ses textes dans la nouvelle langue.",
"confirmCta": "Changer la langue",
"cancel": "Annuler"
},
"patientNotifications": "Notifications patients",
"patientNotificationsDescription": "E-mails envoyés aux patients à propos de leurs dossiers, résultats et approbations en attente",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "frontend",
"version": "0.2.4",
"version": "0.2.5",
"private": true,
"scripts": {
"dev": "next dev",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "temetro",
"version": "0.2.4",
"version": "0.2.5",
"private": true,
"devDependencies": {
"shadcn": "^4.11.0"