Compare commits

...

3 Commits

Author SHA1 Message Date
Khalid Abdi 3767c1689b chore(release): v0.2.1
Bump root, backend, and frontend to 0.2.1 and add the 0.2.1 changelog section.
Patch release: pagination styling, patient-sheet header reflow, message
avatars, and CHANGELOG-based GitHub Release notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:16:22 +03:00
Khalid Abdi edf42e9741 ci: publish the CHANGELOG section as the GitHub Release body
The release workflow only set generate_release_notes, so releases carried just
the auto "Full Changelog" link. Extract this version's section from CHANGELOG.md
into release-notes.md and pass it via body_path (the generated link is still
appended), falling back to a generic line when there's no entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:16:12 +03:00
Khalid Abdi 31adbab87b frontend: fix pagination styling, patient-sheet header, message avatars
- Patients pagination: render the prev/next + page-number controls as COSS
  Buttons inside the Pagination structure. PaginationLink drops its
  buttonVariants styling when given a `render` prop, so the controls were
  unstyled and wrapping; using Button restores proper pill/number buttons.
- Patient detail sheet: move the action buttons (Download summary / Transfer /
  Edit / Delete) to their own wrapping row beneath the identity block so the
  patient name is no longer truncated on the narrow sheet.
- Messages thread: add sender/recipient avatars (MessageAvatar) at the bottom
  of each message group, with a spacer to keep stacked bubbles aligned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:16:12 +03:00
9 changed files with 100 additions and 51 deletions
+18
View File
@@ -62,7 +62,25 @@ jobs:
${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:${{ steps.meta.outputs.version }}
${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:latest
# Pull this version's section out of CHANGELOG.md so the release has real,
# human-written notes (the auto "Full Changelog" link is still appended
# below via generate_release_notes). Falls back to a generic line if the
# version has no CHANGELOG entry.
- name: Extract changelog notes
id: notes
run: |
version="${{ steps.meta.outputs.version }}"
awk -v v="$version" '
$0 ~ "^## \\[" v "\\]" {flag=1; next}
/^## \[/ {flag=0}
flag {print}
' CHANGELOG.md | sed '/./,$!d' > release-notes.md
if [ ! -s release-notes.md ]; then
echo "Release $version. See the changelog for details." > release-notes.md
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
body_path: release-notes.md
generate_release_notes: true
+15
View File
@@ -7,6 +7,21 @@ for how releases are cut and published.
## [Unreleased]
## [0.2.1] — 2026-06-27
### Fixed
- **Patients pagination** controls now render as proper buttons — the prev/next
and page-number controls were unstyled and wrapping (the COSS `PaginationLink`
drops its button styling when given a `render` prop).
- **Patient detail sheet header** reflowed: actions (Download summary / Transfer
/ Edit / Delete) moved to their own wrapping row so the patient name is no
longer truncated.
- **Messages thread** now shows **sender and recipient avatars** alongside the
chat bubbles.
- **Release notes** — the `release` workflow now publishes the matching
`CHANGELOG.md` section as the GitHub Release body (instead of only the
auto-generated "Full Changelog" link).
## [0.2.0] — 2026-06-27
### Added
+1 -1
View File
@@ -9,7 +9,7 @@ information as rich record cards — backed by a **patient-owned data model**.
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
[![Docker images](https://img.shields.io/badge/Docker%20Hub-khalidxv-2496ED?logo=docker&logoColor=white)](https://hub.docker.com/u/khalidxv)
[![Changelog](https://img.shields.io/badge/changelog-0.2.0-success)](./CHANGELOG.md)
[![Changelog](https://img.shields.io/badge/changelog-0.2.1-success)](./CHANGELOG.md)
![temetro AI chat](./.github/assets/screenshot-chat.png)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "temetro-backend",
"version": "0.2.0",
"version": "0.2.1",
"private": true,
"type": "module",
"description": "temetro backend — Express + Postgres API with Better Auth (email/password, organizations) and org-scoped patient records.",
@@ -42,6 +42,7 @@ import { Bubble, BubbleContent } from "@/components/ui/bubble";
import { Button } from "@/components/ui/button";
import {
Message,
MessageAvatar,
MessageContent,
MessageFooter,
MessageHeader,
@@ -200,6 +201,7 @@ export function MessagesView() {
const { t } = useTranslation();
const { data: session } = authClient.useSession();
const myId = session?.user?.id ?? "";
const myInitials = initials(session?.user?.name ?? "");
const router = useRouter();
@@ -694,6 +696,19 @@ export function MessagesView() {
!newDay && (startsGroup ? "mt-4" : "mt-1"),
)}
>
{/* Avatar at the bottom of each run (messenger-style); a
spacer keeps stacked bubbles aligned otherwise. */}
{endsGroup ? (
<MessageAvatar>
<Avatar className="size-8">
<AvatarFallback className="text-[11px]">
{out ? myInitials : initials(m.senderName)}
</AvatarFallback>
</Avatar>
</MessageAvatar>
) : (
<div className="w-8 shrink-0" />
)}
<MessageContent>
{selected.isGroup && !out && startsGroup && (
<MessageHeader>{m.senderName}</MessageHeader>
+28 -23
View File
@@ -238,31 +238,35 @@ export function PatientDetail({
return (
<div className="flex flex-col gap-4">
<div className="flex items-start gap-3">
<Avatar className="size-12">
<AvatarFallback>{patient.initials}</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-1 flex-col gap-1">
<div className="flex flex-wrap items-center gap-2">
<span className="truncate font-semibold text-base text-foreground">
{patient.name}
</span>
<Badge variant={statusVariant[patient.status]}>
{t(`patients.status.${patient.status}`)}
</Badge>
</div>
<span className="text-muted-foreground text-sm">{idLine}</span>
{patient.alerts.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1.5">
{patient.alerts.map((alert) => (
<Badge key={alert} variant="outline">
{alert}
</Badge>
))}
<div className="flex flex-col gap-3">
{/* Identity — full width so the name never gets squeezed by the actions. */}
<div className="flex items-start gap-3">
<Avatar className="size-12">
<AvatarFallback>{patient.initials}</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-1 flex-col gap-1">
<div className="flex flex-wrap items-center gap-2">
<span className="font-semibold text-base text-foreground">
{patient.name}
</span>
<Badge variant={statusVariant[patient.status]}>
{t(`patients.status.${patient.status}`)}
</Badge>
</div>
)}
<span className="text-muted-foreground text-sm">{idLine}</span>
{patient.alerts.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1.5">
{patient.alerts.map((alert) => (
<Badge key={alert} variant="outline">
{alert}
</Badge>
))}
</div>
)}
</div>
</div>
<div className="flex shrink-0 items-center gap-2">
{/* Actions — their own wrapping row beneath the identity. */}
<div className="flex flex-wrap items-center gap-2">
<Button
onClick={() => printPatientSummary(patient, t)}
size="sm"
@@ -292,6 +296,7 @@ export function PatientDetail({
{onDelete && (
<Button
aria-label={t("patients.delete.action")}
className="ml-auto"
onClick={onDelete}
size="sm"
type="button"
+20 -24
View File
@@ -17,10 +17,8 @@ import {
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
import { listPatients, type Patient } from "@/lib/patients";
import { cn } from "@/lib/utils";
// Rows shown per page on the patients table before paginating.
const PAGE_SIZE = 10;
@@ -290,22 +288,20 @@ export function PatientsView() {
>
<PaginationContent>
<PaginationItem>
<PaginationLink
aria-disabled={safePage === 1}
<Button
aria-label={t("patients.pagination.previous")}
className={cn(
"gap-1 px-2.5",
safePage === 1 && "pointer-events-none opacity-50"
)}
className="gap-1"
disabled={safePage === 1}
onClick={() => setPage(Math.max(1, safePage - 1))}
render={<button type="button" />}
size="default"
size="sm"
type="button"
variant="ghost"
>
<ChevronLeft className="size-4" />
<span className="max-sm:hidden">
{t("patients.pagination.previous")}
</span>
</PaginationLink>
</Button>
</PaginationItem>
{pageWindow(safePage, totalPages).map((p, i) =>
p === null ? (
@@ -314,34 +310,34 @@ export function PatientsView() {
</PaginationItem>
) : (
<PaginationItem key={p}>
<PaginationLink
<Button
aria-current={p === safePage ? "page" : undefined}
aria-label={t("patients.pagination.page", { page: p })}
isActive={p === safePage}
onClick={() => setPage(p)}
render={<button type="button" />}
size="icon-sm"
type="button"
variant={p === safePage ? "outline" : "ghost"}
>
{p}
</PaginationLink>
</Button>
</PaginationItem>
)
)}
<PaginationItem>
<PaginationLink
aria-disabled={safePage === totalPages}
<Button
aria-label={t("patients.pagination.next")}
className={cn(
"gap-1 px-2.5",
safePage === totalPages && "pointer-events-none opacity-50"
)}
className="gap-1"
disabled={safePage === totalPages}
onClick={() => setPage(Math.min(totalPages, safePage + 1))}
render={<button type="button" />}
size="default"
size="sm"
type="button"
variant="ghost"
>
<span className="max-sm:hidden">
{t("patients.pagination.next")}
</span>
<ChevronRight className="size-4" />
</PaginationLink>
</Button>
</PaginationItem>
</PaginationContent>
</Pagination>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "frontend",
"version": "0.2.0",
"version": "0.2.1",
"private": true,
"scripts": {
"dev": "next dev",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "temetro",
"version": "0.2.0",
"version": "0.2.1",
"private": true,
"devDependencies": {
"shadcn": "^4.11.0"