chore(release): v1.6.0

Medication editor overhaul, route of administration, today-tile read-flip.

- Route-of-administration column (ORAL | INJECTION | OTHER) decoupled from
  treatmentClass; injection-site picker surfaces for any injection.
  Migration 0088 backfills ORAL via a constant column default.
- One-time injection (oneShot + deliveryForm = INJECTION) editor path.
- Profile-photo upload card on Settings → Account.
- Today-tile read-flip: /api/medications/intake?scope=today and
  /api/dashboard/summary gate through the canonical recurrence engine,
  matching the reminder worker for bi-weekly, rolling, RRULE, and one-time
  cadences.
- Measurement note cap 25 → 200; dashboard range colors on semantic tokens;
  admin sidebar icons + shared-helper consolidation; API-tokens multi-language
  snippets restored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-30 00:10:12 +02:00
1 parent d01d1d1dc9
commit 8ee7aaac44
53 files changed
+1049 -1017

No files matched your search

+13 -2
View File
@@ -252,12 +252,20 @@ export function validateMeasurementRange(
return null;
}
/**
* v1.6.0 — notes cap raised from 25 to 200. A 25-char limit could not
* hold a meaningful clinical note ("took after large meal, felt dizzy
* standing up"). The DB column is unbounded `String?`; this is the
* single source of truth the client char-counters import.
*/
export const MEASUREMENT_NOTES_MAX_LENGTH = 200;
export const createMeasurementSchema = z
.object({
type: measurementTypeEnum,
value: z.number(),
measuredAt: z.iso.datetime({ offset: true }).transform((s) => new Date(s)),
notes: z.string().max(25).optional(),
notes: z.string().max(MEASUREMENT_NOTES_MAX_LENGTH).optional(),
source: measurementSourceEnum.optional().default("MANUAL"),
// Only applies when type === BLOOD_GLUCOSE. Mirrored by a CHECK
// constraint in Postgres (see migration 0021).
@@ -293,7 +301,10 @@ export const updateMeasurementSchema = z.object({
.optional(),
notes: z
.string()
.max(25, "Note cannot exceed 25 characters")
.max(
MEASUREMENT_NOTES_MAX_LENGTH,
`Note cannot exceed ${MEASUREMENT_NOTES_MAX_LENGTH} characters`,
)
.nullable()
.optional(),
});
+18
View File
@@ -38,6 +38,20 @@ export const MEDICATION_TREATMENT_CLASS_VALUES = ["GENERIC", "GLP1"] as const;
export type MedicationTreatmentClass =
(typeof MEDICATION_TREATMENT_CLASS_VALUES)[number];
/**
* v1.6.0 — route of administration. Decoupled from `treatmentClass`:
* the injection-site picker surfaces for any `INJECTION` dose, and a
* one-time injection is `oneShot: true` + `deliveryForm: "INJECTION"`.
* ORAL is the default the migration backfilled onto every existing row.
*/
export const MEDICATION_DELIVERY_FORM_VALUES = [
"ORAL",
"INJECTION",
"OTHER",
] as const;
export type MedicationDeliveryForm =
(typeof MEDICATION_DELIVERY_FORM_VALUES)[number];
/**
* v1.5 — RRULE string shape check.
*
@@ -225,6 +239,8 @@ export const createMedicationSchema = z
treatmentClass: z.enum(MEDICATION_TREATMENT_CLASS_VALUES).optional(),
/** v1.4.25 W4d — doses per pen/vial for inventory tracking. */
dosesPerUnit: z.number().int().min(1).max(100).optional(),
/** v1.6.0 — route of administration (ORAL | INJECTION | OTHER). */
deliveryForm: z.enum(MEDICATION_DELIVERY_FORM_VALUES).optional(),
notificationsEnabled: z.boolean().optional(),
...courseWindowFields,
schedules: z.array(scheduleSchema).min(1, "Mindestens ein Zeitfenster"),
@@ -254,6 +270,8 @@ export const updateMedicationSchema = z
category: z.enum(MEDICATION_CATEGORY_VALUES).optional(),
treatmentClass: z.enum(MEDICATION_TREATMENT_CLASS_VALUES).optional(),
dosesPerUnit: z.number().int().min(1).max(100).nullable().optional(),
/** v1.6.0 — route of administration (ORAL | INJECTION | OTHER). */
deliveryForm: z.enum(MEDICATION_DELIVERY_FORM_VALUES).optional(),
active: z.boolean().optional(),
notificationsEnabled: z.boolean().optional(),
...courseWindowFields,