feat: persist package amount metadata in backend (#356)
* feat: add package amount persistence and backend route support * test: align backend test schemas with medication metadata fields * fix(backend): restore intake usage normalizer for planner endpoint * fix(backend): keep export typing compatible before liquid-unit stack step
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE `medications` ADD `medication_form` text(20) DEFAULT 'tablet' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `medications` ADD `pill_form` text(20);--> statement-breakpoint
|
||||
ALTER TABLE `medications` ADD `lifecycle_category` text(30) DEFAULT 'refill_when_empty' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `medications` ADD `medication_end_date` text;--> statement-breakpoint
|
||||
ALTER TABLE `medications` ADD `auto_mark_obsolete_after_end_date` integer DEFAULT true NOT NULL;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -78,6 +78,13 @@
|
||||
"when": 1771694832866,
|
||||
"tag": "0010_mean_spot",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "6",
|
||||
"when": 1772219947541,
|
||||
"tag": "0011_stiff_randall_flagg",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -125,6 +125,14 @@ export async function runAlterMigrations(client: Client): Promise<{ success: boo
|
||||
`ALTER TABLE medications ADD COLUMN obsolete_at integer`,
|
||||
// Added for explicit medication lifecycle start date
|
||||
`ALTER TABLE medications ADD COLUMN medication_start_date text NOT NULL DEFAULT ''`,
|
||||
// Added for form/lifecycle modeling (V1 medication forms)
|
||||
`ALTER TABLE medications ADD COLUMN medication_form text NOT NULL DEFAULT 'tablet'`,
|
||||
`ALTER TABLE medications ADD COLUMN pill_form text`,
|
||||
`ALTER TABLE medications ADD COLUMN lifecycle_category text NOT NULL DEFAULT 'refill_when_empty'`,
|
||||
`ALTER TABLE medications ADD COLUMN medication_end_date text`,
|
||||
`ALTER TABLE medications ADD COLUMN auto_mark_obsolete_after_end_date integer NOT NULL DEFAULT 1`,
|
||||
`ALTER TABLE medications ADD COLUMN package_amount_value integer NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE medications ADD COLUMN package_amount_unit text NOT NULL DEFAULT 'ml'`,
|
||||
// Added for more detailed reminder info display
|
||||
`ALTER TABLE user_settings ADD COLUMN last_reminder_med_name text`,
|
||||
`ALTER TABLE user_settings ADD COLUMN last_reminder_taken_by text`,
|
||||
|
||||
@@ -29,6 +29,11 @@ export const medications = sqliteTable("medications", {
|
||||
genericName: text("generic_name", { length: 100 }),
|
||||
takenByJson: text("taken_by_json").notNull().default("[]"), // JSON array of person names
|
||||
packageType: text("package_type", { length: 20 }).notNull().default("blister"), // 'blister' or 'bottle'
|
||||
medicationForm: text("medication_form", { length: 20 }).notNull().default("tablet"), // 'capsule' | 'tablet' | 'liquid' | 'topical'
|
||||
pillForm: text("pill_form", { length: 20 }), // Only for blister/bottle with pill-based medications: 'tablet' | 'capsule'
|
||||
lifecycleCategory: text("lifecycle_category", { length: 30 }).notNull().default("refill_when_empty"), // 'refill_when_empty' | 'treatment_period'
|
||||
packageAmountValue: integer("package_amount_value").notNull().default(0), // Informational package quantity (ml/g)
|
||||
packageAmountUnit: text("package_amount_unit", { length: 10 }).notNull().default("ml"), // 'ml' | 'g'
|
||||
packCount: integer("pack_count").notNull().default(1),
|
||||
blistersPerPack: integer("blisters_per_pack").notNull().default(1),
|
||||
pillsPerBlister: integer("pills_per_blister").notNull().default(1),
|
||||
@@ -48,6 +53,10 @@ export const medications = sqliteTable("medications", {
|
||||
notes: text("notes"),
|
||||
intakeRemindersEnabled: integer("intake_reminders_enabled", { mode: "boolean" }).notNull().default(false),
|
||||
medicationStartDate: text("medication_start_date").notNull().default(""),
|
||||
medicationEndDate: text("medication_end_date"),
|
||||
autoMarkObsoleteAfterEndDate: integer("auto_mark_obsolete_after_end_date", { mode: "boolean" })
|
||||
.notNull()
|
||||
.default(true),
|
||||
isObsolete: integer("is_obsolete", { mode: "boolean" }).notNull().default(false),
|
||||
obsoleteAt: integer("obsolete_at", { mode: "timestamp" }),
|
||||
prescriptionEnabled: integer("prescription_enabled", { mode: "boolean" }).notNull().default(false),
|
||||
|
||||
@@ -17,7 +17,7 @@ const IMAGES_DIR = resolve(getDataDir(), "images");
|
||||
// =============================================================================
|
||||
// Export Format Version (bump this when format changes)
|
||||
// =============================================================================
|
||||
const EXPORT_VERSION = "1.1";
|
||||
const EXPORT_VERSION = "1.3";
|
||||
|
||||
// =============================================================================
|
||||
// Zod Schemas for Import Validation
|
||||
@@ -27,6 +27,7 @@ const scheduleSchema = z.object({
|
||||
usage: z.number().nonnegative(),
|
||||
every: z.number().int().min(1),
|
||||
start: z.string(), // ISO datetime string
|
||||
intakeUnit: z.enum(["ml", "tsp", "tbsp"]).nullable().optional(),
|
||||
remind: z.boolean().optional().default(false),
|
||||
takenBy: z.string().nullable().optional(), // Per-intake takenBy (new field)
|
||||
});
|
||||
@@ -38,7 +39,9 @@ const inventorySchema = z.object({
|
||||
totalPills: z.number().int().nullable().optional(), // For bottle type: total capacity
|
||||
looseTablets: z.number().int().min(0).default(0),
|
||||
stockAdjustment: z.number().int().default(0), // Manual stock correction
|
||||
packageType: z.enum(["blister", "bottle"]).default("blister"),
|
||||
packageType: z.enum(["blister", "bottle", "tube", "liquid_container"]).default("blister"),
|
||||
packageAmountValue: z.number().int().min(0).default(0),
|
||||
packageAmountUnit: z.enum(["ml", "g"]).default("ml"),
|
||||
});
|
||||
|
||||
const medicationExportSchema = z.object({
|
||||
@@ -46,11 +49,16 @@ const medicationExportSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
genericName: z.string().nullable().optional(),
|
||||
takenBy: z.array(z.string()).default([]),
|
||||
medicationForm: z.enum(["capsule", "tablet", "liquid", "topical"]).default("tablet"),
|
||||
pillForm: z.enum(["capsule", "tablet"]).nullable().optional(),
|
||||
lifecycleCategory: z.enum(["refill_when_empty", "treatment_period"]).default("refill_when_empty"),
|
||||
inventory: inventorySchema,
|
||||
pillWeightMg: z.number().int().nullable().optional(),
|
||||
doseUnit: z.enum(["mg", "g", "mcg", "ml", "IU", "units", "drops", "puffs"]).default("mg"),
|
||||
schedules: z.array(scheduleSchema).default([]),
|
||||
medicationStartDate: z.string().nullable().optional(),
|
||||
medicationEndDate: z.string().nullable().optional(),
|
||||
autoMarkObsoleteAfterEndDate: z.boolean().default(true),
|
||||
expiryDate: z.string().nullable().optional(),
|
||||
notes: z.string().nullable().optional(),
|
||||
intakeRemindersEnabled: z.boolean().default(false),
|
||||
@@ -155,9 +163,14 @@ async function getUserId(request: FastifyRequest, reply: FastifyReply): Promise<
|
||||
}
|
||||
|
||||
// Parse intakes from DB format to export format (with per-intake takenBy)
|
||||
function parseIntakesForExport(
|
||||
row: typeof medications.$inferSelect
|
||||
): Array<{ usage: number; every: number; start: string; remind: boolean; takenBy: string | null }> {
|
||||
function parseIntakesForExport(row: typeof medications.$inferSelect): Array<{
|
||||
usage: number;
|
||||
every: number;
|
||||
start: string;
|
||||
intakeUnit: "ml" | "tsp" | "tbsp" | null;
|
||||
remind: boolean;
|
||||
takenBy: string | null;
|
||||
}> {
|
||||
// Use the new parseIntakesJson which falls back to legacy format
|
||||
const intakes = parseIntakesJson(
|
||||
row.intakesJson,
|
||||
@@ -169,6 +182,7 @@ function parseIntakesForExport(
|
||||
usage: intake.usage,
|
||||
every: intake.every,
|
||||
start: intake.start,
|
||||
intakeUnit: null,
|
||||
remind: intake.intakeRemindersEnabled,
|
||||
takenBy: intake.takenBy, // Per-intake takenBy
|
||||
}));
|
||||
@@ -295,6 +309,9 @@ export async function exportRoutes(app: FastifyInstance) {
|
||||
name: med.name,
|
||||
genericName: med.genericName,
|
||||
takenBy: parseTakenByJson(med.takenByJson),
|
||||
medicationForm: med.medicationForm ?? "tablet",
|
||||
pillForm: med.pillForm ?? null,
|
||||
lifecycleCategory: med.lifecycleCategory ?? "refill_when_empty",
|
||||
inventory: {
|
||||
packCount: med.packCount ?? 1,
|
||||
blistersPerPack: med.blistersPerPack ?? 1,
|
||||
@@ -303,11 +320,15 @@ export async function exportRoutes(app: FastifyInstance) {
|
||||
looseTablets: med.looseTablets ?? 0,
|
||||
stockAdjustment: med.stockAdjustment ?? 0,
|
||||
packageType: med.packageType ?? "blister",
|
||||
packageAmountValue: med.packageAmountValue ?? 0,
|
||||
packageAmountUnit: (med.packageAmountUnit ?? "ml") as "ml" | "g",
|
||||
},
|
||||
pillWeightMg: med.pillWeightMg,
|
||||
doseUnit: med.doseUnit ?? "mg",
|
||||
schedules: parseIntakesForExport(med),
|
||||
medicationStartDate: med.medicationStartDate || null,
|
||||
medicationEndDate: med.medicationEndDate || null,
|
||||
autoMarkObsoleteAfterEndDate: med.autoMarkObsoleteAfterEndDate ?? true,
|
||||
expiryDate: med.expiryDate,
|
||||
notes: med.notes,
|
||||
intakeRemindersEnabled: med.intakeRemindersEnabled ?? false,
|
||||
@@ -555,6 +576,7 @@ export async function exportRoutes(app: FastifyInstance) {
|
||||
usage: s.usage,
|
||||
every: s.every,
|
||||
start: s.start,
|
||||
intakeUnit: s.intakeUnit ?? null,
|
||||
takenBy: s.takenBy || null,
|
||||
intakeRemindersEnabled: s.remind ?? false,
|
||||
}))
|
||||
@@ -570,7 +592,12 @@ export async function exportRoutes(app: FastifyInstance) {
|
||||
name: med.name,
|
||||
genericName: med.genericName || null,
|
||||
takenByJson,
|
||||
medicationForm: med.medicationForm ?? "tablet",
|
||||
pillForm: med.pillForm || null,
|
||||
lifecycleCategory: med.lifecycleCategory ?? "refill_when_empty",
|
||||
packageType: med.inventory.packageType ?? "blister",
|
||||
packageAmountValue: med.inventory.packageAmountValue ?? 0,
|
||||
packageAmountUnit: med.inventory.packageAmountUnit ?? "ml",
|
||||
packCount: med.inventory.packCount,
|
||||
blistersPerPack: med.inventory.blistersPerPack,
|
||||
pillsPerBlister: med.inventory.pillsPerBlister,
|
||||
@@ -581,6 +608,8 @@ export async function exportRoutes(app: FastifyInstance) {
|
||||
pillWeightMg: med.pillWeightMg || null,
|
||||
doseUnit: med.doseUnit ?? "mg",
|
||||
medicationStartDate: med.medicationStartDate || "",
|
||||
medicationEndDate: med.medicationEndDate || null,
|
||||
autoMarkObsoleteAfterEndDate: med.autoMarkObsoleteAfterEndDate ?? true,
|
||||
intakesJson,
|
||||
usageJson,
|
||||
everyJson,
|
||||
|
||||
@@ -14,7 +14,13 @@ import {
|
||||
streamToBuffer,
|
||||
writeOptimizedImageSet,
|
||||
} from "../utils/image-upload.js";
|
||||
import { type Intake, parseIntakesJson, parseLocalDateTime, parseTakenByJson } from "../utils/scheduler-utils.js";
|
||||
import {
|
||||
type Intake,
|
||||
normalizeIntakeUsageForStock,
|
||||
parseIntakesJson,
|
||||
parseLocalDateTime,
|
||||
parseTakenByJson,
|
||||
} from "../utils/scheduler-utils.js";
|
||||
|
||||
const IMAGES_DIR = resolve(getDataDir(), "images");
|
||||
|
||||
@@ -23,6 +29,7 @@ const intakeSchema = z.object({
|
||||
usage: z.number().nonnegative(),
|
||||
every: z.number().int().min(1),
|
||||
start: z.string().datetime({ local: true }),
|
||||
intakeUnit: z.enum(["ml", "tsp", "tbsp"]).nullable().optional(),
|
||||
takenBy: z.string().trim().max(100).nullable().optional(), // Person for this specific intake
|
||||
intakeRemindersEnabled: z.boolean().default(false), // Per-intake reminder setting
|
||||
});
|
||||
@@ -34,26 +41,37 @@ const blisterSchema = z.object({
|
||||
start: z.string().datetime({ local: true }),
|
||||
});
|
||||
|
||||
const packageTypeSchema = z.enum(["blister", "bottle"]).default("blister");
|
||||
const packageTypeSchema = z.enum(["blister", "bottle", "tube", "liquid_container"]).default("blister");
|
||||
const medicationFormSchema = z.enum(["capsule", "tablet", "liquid", "topical"]).default("tablet");
|
||||
const pillFormSchema = z.enum(["capsule", "tablet"]);
|
||||
const lifecycleCategorySchema = z.enum(["refill_when_empty", "treatment_period"]).default("refill_when_empty");
|
||||
const doseUnitSchema = z.enum(["mg", "g", "mcg", "ml", "IU", "units", "drops", "puffs"]).default("mg");
|
||||
const medicationStartDateSchema = z
|
||||
.union([z.string().regex(/^\d{4}-\d{2}-\d{2}$/), z.literal(""), z.null()])
|
||||
.optional();
|
||||
const medicationEndDateSchema = z.union([z.string().regex(/^\d{4}-\d{2}-\d{2}$/), z.literal(""), z.null()]).optional();
|
||||
|
||||
const medicationSchema = z
|
||||
.object({
|
||||
name: z.string().trim().max(100).default(""),
|
||||
genericName: z.string().trim().max(100).nullable().optional(),
|
||||
takenBy: z.array(z.string().trim().max(100)).default([]), // Medication-level takenBy (fallback)
|
||||
medicationForm: medicationFormSchema,
|
||||
pillForm: pillFormSchema.nullable().optional(),
|
||||
lifecycleCategory: lifecycleCategorySchema,
|
||||
packageType: packageTypeSchema,
|
||||
packCount: z.number().int().min(0).default(1),
|
||||
blistersPerPack: z.number().int().min(1).default(1),
|
||||
pillsPerBlister: z.number().int().min(1).default(1),
|
||||
packageAmountValue: z.number().int().min(0).default(0),
|
||||
packageAmountUnit: z.enum(["ml", "g"]).default("ml"),
|
||||
totalPills: z.number().int().min(1).nullable().optional(), // For bottle type: total capacity
|
||||
looseTablets: z.number().int().min(0).default(0),
|
||||
pillWeightMg: z.number().nonnegative().nullable().optional(),
|
||||
doseUnit: doseUnitSchema,
|
||||
medicationStartDate: medicationStartDateSchema,
|
||||
medicationEndDate: medicationEndDateSchema,
|
||||
autoMarkObsoleteAfterEndDate: z.boolean().default(true),
|
||||
expiryDate: z.string().nullable().optional(),
|
||||
notes: z.string().max(2000).nullable().optional(),
|
||||
prescriptionEnabled: z.boolean().default(false),
|
||||
@@ -84,6 +102,77 @@ const medicationSchema = z
|
||||
path: ["medicationStartDate"],
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
const startDate = data.medicationStartDate ?? "";
|
||||
const endDate = data.medicationEndDate ?? "";
|
||||
if (!startDate || !endDate) return true;
|
||||
return startDate <= endDate;
|
||||
},
|
||||
{
|
||||
message: "Medication end date must be on or after medication start date",
|
||||
path: ["medicationEndDate"],
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.medicationForm === "capsule" || data.medicationForm === "tablet") {
|
||||
return data.pillForm == null || data.pillForm === "capsule" || data.pillForm === "tablet";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "pillForm must be capsule or tablet for capsule/tablet medications",
|
||||
path: ["pillForm"],
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.medicationForm === "topical") {
|
||||
return data.packageType === "tube";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Topical medications must use tube package type",
|
||||
path: ["packageType"],
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.medicationForm === "liquid") {
|
||||
return data.packageType === "liquid_container";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Liquid medications must use liquid_container package type",
|
||||
path: ["packageType"],
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.medicationForm === "capsule" || data.medicationForm === "tablet") {
|
||||
return data.packageType !== "tube" && data.packageType !== "liquid_container";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Capsule and tablet medications cannot use tube or liquid_container package type",
|
||||
path: ["packageType"],
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
const schedules = data.intakes ?? data.blisters ?? [];
|
||||
if (data.pillForm !== "capsule") return true;
|
||||
return schedules.every((entry) => Number.isInteger(entry.usage));
|
||||
},
|
||||
{
|
||||
message: "Fractional intake is not allowed for capsule",
|
||||
path: ["intakes"],
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (!data.prescriptionEnabled) return true;
|
||||
@@ -131,6 +220,26 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
app.get<{ Querystring: { includeObsolete?: string } }>("/medications", async (request, reply) => {
|
||||
const userId = await getUserId(request, reply);
|
||||
const includeObsolete = request.query.includeObsolete === "true";
|
||||
const initialRows = await db
|
||||
.select()
|
||||
.from(medications)
|
||||
.where(eq(medications.userId, userId))
|
||||
.orderBy(medications.id);
|
||||
const todayDate = new Date().toISOString().slice(0, 10);
|
||||
|
||||
for (const row of initialRows) {
|
||||
if (row.isObsolete) continue;
|
||||
if (!(row.autoMarkObsoleteAfterEndDate ?? true)) continue;
|
||||
const endDate = row.medicationEndDate?.slice(0, 10);
|
||||
if (!endDate) continue;
|
||||
if (endDate > todayDate) continue;
|
||||
|
||||
await db
|
||||
.update(medications)
|
||||
.set({ isObsolete: true, obsoleteAt: new Date(), updatedAt: new Date() })
|
||||
.where(and(eq(medications.id, row.id), eq(medications.userId, userId)));
|
||||
}
|
||||
|
||||
const whereClause = includeObsolete
|
||||
? eq(medications.userId, userId)
|
||||
: and(eq(medications.userId, userId), eq(medications.isObsolete, false));
|
||||
@@ -148,10 +257,15 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
name: row.name,
|
||||
genericName: row.genericName,
|
||||
takenBy: parseTakenByJson(row.takenByJson),
|
||||
medicationForm: row.medicationForm ?? "tablet",
|
||||
pillForm: row.pillForm ?? null,
|
||||
lifecycleCategory: row.lifecycleCategory ?? "refill_when_empty",
|
||||
packageType: row.packageType ?? "blister",
|
||||
packCount: row.packCount ?? 1,
|
||||
blistersPerPack: row.blistersPerPack ?? 1,
|
||||
pillsPerBlister: row.pillsPerBlister ?? 1,
|
||||
packageAmountValue: row.packageAmountValue ?? 0,
|
||||
packageAmountUnit: (row.packageAmountUnit ?? "ml") as "ml" | "g",
|
||||
totalPills: row.totalPills ?? null,
|
||||
looseTablets: row.looseTablets ?? 0,
|
||||
stockAdjustment: row.stockAdjustment ?? 0,
|
||||
@@ -159,6 +273,8 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
pillWeightMg: row.pillWeightMg,
|
||||
doseUnit: row.doseUnit ?? "mg",
|
||||
medicationStartDate: row.medicationStartDate || null,
|
||||
medicationEndDate: row.medicationEndDate || null,
|
||||
autoMarkObsoleteAfterEndDate: row.autoMarkObsoleteAfterEndDate ?? true,
|
||||
intakes, // New unified format with per-intake takenBy
|
||||
// Legacy blisters format (for backward compat with frontend during transition)
|
||||
blisters: intakes.map((i) => ({ usage: i.usage, every: i.every, start: i.start })),
|
||||
@@ -188,15 +304,22 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
name,
|
||||
genericName,
|
||||
takenBy,
|
||||
medicationForm,
|
||||
pillForm,
|
||||
lifecycleCategory,
|
||||
packageType,
|
||||
packCount,
|
||||
blistersPerPack,
|
||||
pillsPerBlister,
|
||||
packageAmountValue,
|
||||
packageAmountUnit,
|
||||
totalPills,
|
||||
looseTablets,
|
||||
pillWeightMg,
|
||||
doseUnit,
|
||||
medicationStartDate,
|
||||
medicationEndDate,
|
||||
autoMarkObsoleteAfterEndDate,
|
||||
expiryDate,
|
||||
notes,
|
||||
prescriptionEnabled,
|
||||
@@ -209,6 +332,9 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
blisters: inputBlisters,
|
||||
} = parsed.data;
|
||||
|
||||
const normalizedPillForm =
|
||||
medicationForm === "capsule" || medicationForm === "tablet" ? (pillForm ?? medicationForm) : null;
|
||||
|
||||
// Convert to unified intakes format
|
||||
let intakes: Intake[];
|
||||
if (inputIntakes) {
|
||||
@@ -217,6 +343,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
usage: i.usage,
|
||||
every: i.every,
|
||||
start: i.start,
|
||||
intakeUnit: i.intakeUnit ?? null,
|
||||
takenBy: i.takenBy || null,
|
||||
intakeRemindersEnabled: i.intakeRemindersEnabled ?? false,
|
||||
}));
|
||||
@@ -226,6 +353,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
usage: b.usage,
|
||||
every: b.every,
|
||||
start: b.start,
|
||||
intakeUnit: null,
|
||||
takenBy: null, // No per-intake takenBy from legacy
|
||||
intakeRemindersEnabled: intakeRemindersEnabled ?? false,
|
||||
}));
|
||||
@@ -247,15 +375,22 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
name,
|
||||
genericName: genericName || null,
|
||||
takenByJson,
|
||||
medicationForm: medicationForm ?? "tablet",
|
||||
pillForm: normalizedPillForm,
|
||||
lifecycleCategory: lifecycleCategory ?? "refill_when_empty",
|
||||
packageType: packageType ?? "blister",
|
||||
packCount,
|
||||
blistersPerPack,
|
||||
pillsPerBlister,
|
||||
packageAmountValue,
|
||||
packageAmountUnit,
|
||||
totalPills: totalPills || null,
|
||||
looseTablets,
|
||||
pillWeightMg: pillWeightMg || null,
|
||||
doseUnit: doseUnit ?? "mg",
|
||||
medicationStartDate: medicationStartDate ?? "",
|
||||
medicationEndDate: medicationEndDate || null,
|
||||
autoMarkObsoleteAfterEndDate: autoMarkObsoleteAfterEndDate ?? true,
|
||||
expiryDate: expiryDate || null,
|
||||
notes: notes || null,
|
||||
prescriptionEnabled: prescriptionEnabled ?? false,
|
||||
@@ -276,10 +411,15 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
name: inserted.name,
|
||||
genericName: inserted.genericName,
|
||||
takenBy: parseTakenByJson(inserted.takenByJson),
|
||||
medicationForm: inserted.medicationForm ?? "tablet",
|
||||
pillForm: inserted.pillForm ?? null,
|
||||
lifecycleCategory: inserted.lifecycleCategory ?? "refill_when_empty",
|
||||
packageType: inserted.packageType ?? "blister",
|
||||
packCount: inserted.packCount,
|
||||
blistersPerPack: inserted.blistersPerPack,
|
||||
pillsPerBlister: inserted.pillsPerBlister,
|
||||
packageAmountValue: inserted.packageAmountValue ?? 0,
|
||||
packageAmountUnit: (inserted.packageAmountUnit ?? "ml") as "ml" | "g",
|
||||
totalPills: inserted.totalPills ?? null,
|
||||
looseTablets: inserted.looseTablets,
|
||||
stockAdjustment: inserted.stockAdjustment ?? 0,
|
||||
@@ -287,6 +427,8 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
pillWeightMg: inserted.pillWeightMg,
|
||||
doseUnit: inserted.doseUnit ?? "mg",
|
||||
medicationStartDate: inserted.medicationStartDate || null,
|
||||
medicationEndDate: inserted.medicationEndDate || null,
|
||||
autoMarkObsoleteAfterEndDate: inserted.autoMarkObsoleteAfterEndDate ?? true,
|
||||
intakes,
|
||||
blisters: intakes.map((i) => ({ usage: i.usage, every: i.every, start: i.start })),
|
||||
imageUrl: inserted.imageUrl,
|
||||
@@ -323,15 +465,22 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
name,
|
||||
genericName,
|
||||
takenBy,
|
||||
medicationForm,
|
||||
pillForm,
|
||||
lifecycleCategory,
|
||||
packageType,
|
||||
packCount,
|
||||
blistersPerPack,
|
||||
pillsPerBlister,
|
||||
packageAmountValue,
|
||||
packageAmountUnit,
|
||||
totalPills,
|
||||
looseTablets,
|
||||
pillWeightMg,
|
||||
doseUnit,
|
||||
medicationStartDate,
|
||||
medicationEndDate,
|
||||
autoMarkObsoleteAfterEndDate,
|
||||
expiryDate,
|
||||
notes,
|
||||
prescriptionEnabled,
|
||||
@@ -344,6 +493,9 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
blisters: inputBlisters,
|
||||
} = parsed.data;
|
||||
|
||||
const normalizedPillForm =
|
||||
medicationForm === "capsule" || medicationForm === "tablet" ? (pillForm ?? medicationForm) : null;
|
||||
|
||||
// Convert to unified intakes format
|
||||
let intakes: Intake[];
|
||||
if (inputIntakes) {
|
||||
@@ -352,6 +504,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
usage: i.usage,
|
||||
every: i.every,
|
||||
start: i.start,
|
||||
intakeUnit: i.intakeUnit ?? null,
|
||||
takenBy: i.takenBy || null,
|
||||
intakeRemindersEnabled: i.intakeRemindersEnabled ?? false,
|
||||
}));
|
||||
@@ -361,6 +514,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
usage: b.usage,
|
||||
every: b.every,
|
||||
start: b.start,
|
||||
intakeUnit: null,
|
||||
takenBy: null, // No per-intake takenBy from legacy
|
||||
intakeRemindersEnabled: intakeRemindersEnabled ?? false,
|
||||
}));
|
||||
@@ -392,15 +546,22 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
name,
|
||||
genericName: genericName || null,
|
||||
takenByJson,
|
||||
medicationForm: medicationForm ?? "tablet",
|
||||
pillForm: normalizedPillForm,
|
||||
lifecycleCategory: lifecycleCategory ?? "refill_when_empty",
|
||||
packageType: packageType ?? "blister",
|
||||
packCount,
|
||||
blistersPerPack,
|
||||
pillsPerBlister,
|
||||
totalPills: totalPills || null,
|
||||
packageAmountValue,
|
||||
packageAmountUnit,
|
||||
looseTablets,
|
||||
pillWeightMg: pillWeightMg || null,
|
||||
doseUnit: doseUnit ?? "mg",
|
||||
medicationStartDate: medicationStartDate ?? "",
|
||||
medicationEndDate: medicationEndDate || null,
|
||||
autoMarkObsoleteAfterEndDate: autoMarkObsoleteAfterEndDate ?? true,
|
||||
expiryDate: expiryDate || null,
|
||||
notes: notes || null,
|
||||
prescriptionEnabled: prescriptionEnabled ?? false,
|
||||
@@ -545,10 +706,15 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
name: result[0].name,
|
||||
genericName: result[0].genericName,
|
||||
takenBy: parseTakenByJson(result[0].takenByJson),
|
||||
medicationForm: result[0].medicationForm ?? "tablet",
|
||||
pillForm: result[0].pillForm ?? null,
|
||||
lifecycleCategory: result[0].lifecycleCategory ?? "refill_when_empty",
|
||||
packageType: result[0].packageType ?? "blister",
|
||||
packCount: result[0].packCount,
|
||||
blistersPerPack: result[0].blistersPerPack,
|
||||
pillsPerBlister: result[0].pillsPerBlister,
|
||||
packageAmountValue: result[0].packageAmountValue ?? 0,
|
||||
packageAmountUnit: (result[0].packageAmountUnit ?? "ml") as "ml" | "g",
|
||||
totalPills: result[0].totalPills ?? null,
|
||||
looseTablets: result[0].looseTablets,
|
||||
stockAdjustment: result[0].stockAdjustment ?? 0,
|
||||
@@ -556,6 +722,8 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
pillWeightMg: result[0].pillWeightMg,
|
||||
doseUnit: result[0].doseUnit ?? "mg",
|
||||
medicationStartDate: result[0].medicationStartDate || null,
|
||||
medicationEndDate: result[0].medicationEndDate || null,
|
||||
autoMarkObsoleteAfterEndDate: result[0].autoMarkObsoleteAfterEndDate ?? true,
|
||||
intakes,
|
||||
blisters: intakes.map((i) => ({ usage: i.usage, every: i.every, start: i.start })),
|
||||
imageUrl: result[0].imageUrl,
|
||||
@@ -845,7 +1013,12 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
{ usageJson: row.usageJson, everyJson: row.everyJson, startJson: row.startJson },
|
||||
row.intakeRemindersEnabled ?? false
|
||||
);
|
||||
const blisters = intakes.map((i) => ({ usage: i.usage, every: i.every, start: i.start }));
|
||||
const medForm = row.medicationForm ?? "tablet";
|
||||
const blisters = intakes.map((i) => ({
|
||||
usage: normalizeIntakeUsageForStock(i, medForm, row.packageType),
|
||||
every: i.every,
|
||||
start: i.start,
|
||||
}));
|
||||
const pillsPerBlister = row.pillsPerBlister ?? 1;
|
||||
const packCount = row.packCount ?? 1;
|
||||
const blistersPerPack = row.blistersPerPack ?? 1;
|
||||
@@ -854,8 +1027,9 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
const packageType = row.packageType ?? "blister";
|
||||
|
||||
// For bottle type, looseTablets IS the current stock (no blister math)
|
||||
const isTopical = medForm === "topical" || packageType === "tube";
|
||||
const originalTotalPills =
|
||||
packageType === "bottle"
|
||||
packageType === "bottle" || packageType === "liquid_container"
|
||||
? looseTablets + stockAdjustment
|
||||
: packCount * blistersPerPack * pillsPerBlister + looseTablets + stockAdjustment;
|
||||
|
||||
@@ -867,7 +1041,9 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
let consumedUntilNow = 0;
|
||||
const msPerDay = 86400000;
|
||||
|
||||
if (stockCalculationMode === "automatic") {
|
||||
if (isTopical) {
|
||||
consumedUntilNow = 0;
|
||||
} else if (stockCalculationMode === "automatic") {
|
||||
blisters.forEach((blister, blisterIdx) => {
|
||||
const blisterStart = parseLocalDateTime(blister.start).getTime();
|
||||
if (Number.isNaN(blisterStart)) return;
|
||||
@@ -963,7 +1139,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
const currentStock = Math.max(0, originalTotalPills - consumedUntilNow);
|
||||
const currentStock = isTopical ? originalTotalPills : Math.max(0, originalTotalPills - consumedUntilNow);
|
||||
|
||||
// Calculate usage for the planning period
|
||||
// Always use the user-selected start date for the usage calculation.
|
||||
@@ -973,7 +1149,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
// The stock already reflects consumed doses, so no double-counting occurs.
|
||||
// When includeUntilStart is true, calculate from now to end (useful for trip planning)
|
||||
const effectivePlannerStart = includeUntilStart ? now : start;
|
||||
const usageTotal = calculateUsageInRange(blisters, effectivePlannerStart, end);
|
||||
const usageTotal = isTopical ? 0 : calculateUsageInRange(blisters, effectivePlannerStart, end);
|
||||
|
||||
const blistersNeeded = pillsPerBlister > 0 ? Math.ceil(usageTotal / pillsPerBlister) : 0;
|
||||
|
||||
@@ -983,7 +1159,7 @@ export async function medicationRoutes(app: FastifyInstance) {
|
||||
let fullBlisters: number;
|
||||
let loosePills: number;
|
||||
|
||||
if (packageType === "bottle") {
|
||||
if (packageType === "bottle" || packageType === "tube" || packageType === "liquid_container") {
|
||||
// Bottle type: no blisters, everything is loose pills
|
||||
fullBlisters = 0;
|
||||
loosePills = availableAfterPeriod;
|
||||
|
||||
@@ -32,8 +32,8 @@ async function loadDbClientModule(options: ClientTestOptions = {}) {
|
||||
.mockReturnValue(dirWritable ? { success: true } : { success: false, error: "permission denied" });
|
||||
const getDbPaths = vi.fn().mockReturnValue({
|
||||
dataDir: "/tmp/medassist-data",
|
||||
dbPath: "/tmp/medassist-data/medassist.db",
|
||||
url: "file:/tmp/medassist-data/medassist.db",
|
||||
dbPath: "/tmp/medassist-data/medassist-ng.db",
|
||||
url: "file:/tmp/medassist-data/medassist-ng.db",
|
||||
});
|
||||
const runDrizzleMigrations = vi.fn().mockResolvedValue({ success: true });
|
||||
const runAlterMigrations = vi.fn().mockResolvedValue({ errors: [] });
|
||||
@@ -102,7 +102,7 @@ describe("db/client bootstrap", () => {
|
||||
await mod.migrationsReady;
|
||||
|
||||
expect(mocks.ensureDataDirectory).toHaveBeenCalledWith("/tmp/medassist-data");
|
||||
expect(mocks.createClient).toHaveBeenCalledWith({ url: "file:/tmp/medassist-data/medassist.db" });
|
||||
expect(mocks.createClient).toHaveBeenCalledWith({ url: "file:/tmp/medassist-data/medassist-ng.db" });
|
||||
expect(mocks.runDrizzleMigrations).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.runAlterMigrations).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.repairTrailingHyphenDoseIds).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -82,7 +82,12 @@ async function createSchema(client: Client) {
|
||||
name text NOT NULL,
|
||||
generic_name text,
|
||||
taken_by_json text NOT NULL DEFAULT '[]',
|
||||
medication_form text NOT NULL DEFAULT 'tablet',
|
||||
pill_form text,
|
||||
lifecycle_category text NOT NULL DEFAULT 'refill_when_empty',
|
||||
package_type text NOT NULL DEFAULT 'blister',
|
||||
package_amount_value integer NOT NULL DEFAULT 0,
|
||||
package_amount_unit text NOT NULL DEFAULT 'ml',
|
||||
pack_count integer NOT NULL DEFAULT 1,
|
||||
blisters_per_pack integer NOT NULL DEFAULT 1,
|
||||
pills_per_blister integer NOT NULL DEFAULT 1,
|
||||
@@ -101,6 +106,8 @@ async function createSchema(client: Client) {
|
||||
notes text,
|
||||
intake_reminders_enabled integer NOT NULL DEFAULT 0,
|
||||
medication_start_date text NOT NULL DEFAULT '',
|
||||
medication_end_date text,
|
||||
auto_mark_obsolete_after_end_date integer NOT NULL DEFAULT 1,
|
||||
is_obsolete integer NOT NULL DEFAULT 0,
|
||||
obsolete_at integer,
|
||||
prescription_enabled integer NOT NULL DEFAULT 0,
|
||||
@@ -2499,10 +2506,10 @@ describe("E2E Tests with Real Routes", () => {
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Package Type (bottle vs blister) Tests
|
||||
// Package Type (blister, bottle, liquid_container) Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("Package type handling (bottle vs blister)", () => {
|
||||
describe("Package type handling (blister, bottle, liquid_container)", () => {
|
||||
const bottleMedication = {
|
||||
name: "Vitamin D Drops",
|
||||
packageType: "bottle",
|
||||
@@ -2523,6 +2530,18 @@ describe("E2E Tests with Real Routes", () => {
|
||||
blisters: [{ usage: 1, every: 1, start: "2025-01-01T08:00:00.000Z" }],
|
||||
};
|
||||
|
||||
const liquidContainerMedication = {
|
||||
name: "Cough Syrup",
|
||||
medicationForm: "liquid",
|
||||
packageType: "liquid_container",
|
||||
doseUnit: "ml",
|
||||
packCount: 0,
|
||||
blistersPerPack: 1,
|
||||
pillsPerBlister: 1,
|
||||
looseTablets: 180,
|
||||
blisters: [{ usage: 5, every: 1, start: "2025-01-01T08:00:00.000Z" }],
|
||||
};
|
||||
|
||||
it("should create and return bottle type medication", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
@@ -2567,6 +2586,49 @@ describe("E2E Tests with Real Routes", () => {
|
||||
expect(data.medications[0].totalPills).toBe(120);
|
||||
});
|
||||
|
||||
it("should create and return liquid_container type medication", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/medications",
|
||||
payload: liquidContainerMedication,
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const data = response.json();
|
||||
expect(data.packageType).toBe("liquid_container");
|
||||
expect(data.medicationForm).toBe("liquid");
|
||||
expect(data.doseUnit).toBe("ml");
|
||||
expect(data.looseTablets).toBe(180);
|
||||
});
|
||||
|
||||
it("should return packageType and ml-based stock semantics in shared schedule for liquid_container", async () => {
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
url: "/medications",
|
||||
payload: { ...liquidContainerMedication, takenBy: ["Daniel"] },
|
||||
});
|
||||
|
||||
const shareResponse = await app.inject({
|
||||
method: "POST",
|
||||
url: "/share",
|
||||
payload: { takenBy: "Daniel", scheduleDays: 30 },
|
||||
});
|
||||
expect(shareResponse.statusCode).toBe(200);
|
||||
const { token } = shareResponse.json();
|
||||
|
||||
const scheduleResponse = await app.inject({
|
||||
method: "GET",
|
||||
url: `/share/${token}`,
|
||||
});
|
||||
|
||||
expect(scheduleResponse.statusCode).toBe(200);
|
||||
const data = scheduleResponse.json();
|
||||
expect(data.medications).toHaveLength(1);
|
||||
expect(data.medications[0].packageType).toBe("liquid_container");
|
||||
// Liquid container follows container semantics (stock from looseTablets only).
|
||||
expect(data.medications[0].totalPills).toBe(180);
|
||||
});
|
||||
|
||||
it("should calculate correct totalPills for shared blister medication", async () => {
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
@@ -2742,5 +2804,18 @@ describe("E2E Tests with Real Routes", () => {
|
||||
expect(medsResponse.json()).toHaveLength(1);
|
||||
expect(medsResponse.json()[0].packageType).toBe("blister");
|
||||
});
|
||||
|
||||
it("should reject liquid medication form with non-liquid package type", async () => {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/medications",
|
||||
payload: {
|
||||
...liquidContainerMedication,
|
||||
packageType: "bottle",
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -76,7 +76,12 @@ async function createSchema(client: Client) {
|
||||
name text NOT NULL,
|
||||
generic_name text,
|
||||
taken_by_json text NOT NULL DEFAULT '[]',
|
||||
medication_form text NOT NULL DEFAULT 'tablet',
|
||||
pill_form text,
|
||||
lifecycle_category text NOT NULL DEFAULT 'refill_when_empty',
|
||||
package_type text NOT NULL DEFAULT 'blister',
|
||||
package_amount_value integer NOT NULL DEFAULT 0,
|
||||
package_amount_unit text NOT NULL DEFAULT 'ml',
|
||||
pack_count integer NOT NULL DEFAULT 1,
|
||||
blisters_per_pack integer NOT NULL DEFAULT 1,
|
||||
pills_per_blister integer NOT NULL DEFAULT 1,
|
||||
@@ -95,6 +100,8 @@ async function createSchema(client: Client) {
|
||||
notes text,
|
||||
intake_reminders_enabled integer NOT NULL DEFAULT 0,
|
||||
medication_start_date text NOT NULL DEFAULT '',
|
||||
medication_end_date text,
|
||||
auto_mark_obsolete_after_end_date integer NOT NULL DEFAULT 1,
|
||||
is_obsolete integer NOT NULL DEFAULT 0,
|
||||
obsolete_at integer,
|
||||
prescription_enabled integer NOT NULL DEFAULT 0,
|
||||
|
||||
@@ -93,7 +93,12 @@ async function createSchema(client: Client) {
|
||||
name text NOT NULL,
|
||||
generic_name text,
|
||||
taken_by_json text NOT NULL DEFAULT '[]',
|
||||
medication_form text NOT NULL DEFAULT 'tablet',
|
||||
pill_form text,
|
||||
lifecycle_category text NOT NULL DEFAULT 'refill_when_empty',
|
||||
package_type text NOT NULL DEFAULT 'blister',
|
||||
package_amount_value integer NOT NULL DEFAULT 0,
|
||||
package_amount_unit text NOT NULL DEFAULT 'ml',
|
||||
pack_count integer NOT NULL DEFAULT 1,
|
||||
blisters_per_pack integer NOT NULL DEFAULT 1,
|
||||
pills_per_blister integer NOT NULL DEFAULT 1,
|
||||
@@ -112,6 +117,8 @@ async function createSchema(client: Client) {
|
||||
notes text,
|
||||
intake_reminders_enabled integer NOT NULL DEFAULT 0,
|
||||
medication_start_date text NOT NULL DEFAULT '',
|
||||
medication_end_date text,
|
||||
auto_mark_obsolete_after_end_date integer NOT NULL DEFAULT 1,
|
||||
is_obsolete integer NOT NULL DEFAULT 0,
|
||||
obsolete_at integer,
|
||||
prescription_enabled integer NOT NULL DEFAULT 0,
|
||||
|
||||
@@ -17,6 +17,22 @@ export type Intake = {
|
||||
intakeRemindersEnabled: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize intake usage for stock math.
|
||||
*
|
||||
* Stock semantics currently treat numeric usage as-is for all supported
|
||||
* medication forms/package types. The helper centralizes this behavior so route
|
||||
* logic can depend on a single validated numeric value.
|
||||
*/
|
||||
export function normalizeIntakeUsageForStock(
|
||||
intake: Pick<Intake, "usage">,
|
||||
_medicationForm?: string | null,
|
||||
_packageType?: string | null
|
||||
): number {
|
||||
const usage = Number(intake.usage);
|
||||
return Number.isFinite(usage) && usage > 0 ? usage : 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Timezone utilities
|
||||
// =============================================================================
|
||||
|
||||
@@ -23,6 +23,607 @@ Use this block for each meaningful task:
|
||||
|
||||
## Entries
|
||||
|
||||
### 2026-02-28 (CI triage: Backend Tests failure on PR #356)
|
||||
|
||||
- 🧩 Task: Reproduce and fix `Backend Tests` CI failure on `feat/package-amount-backend`.
|
||||
- ✅ Decisions:
|
||||
- Reproduced local backend failure (`15` failing tests) with `500` responses from `POST /medications/usage`.
|
||||
- Identified root cause: `backend/src/routes/medications.ts` called `normalizeIntakeUsageForStock(...)` but helper export was missing from `backend/src/utils/scheduler-utils.ts`.
|
||||
- Added minimal helper implementation that normalizes finite positive usage values and keeps stock usage semantics centralized.
|
||||
- Re-ran targeted failing suites, then full backend suite; all passed.
|
||||
- 📁 Files touched:
|
||||
- `backend/src/utils/scheduler-utils.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- None for backend test check; release handoff can proceed without push from this agent.
|
||||
|
||||
### 2026-02-28 (stacked branch validation: package amount + liquid units + topical no-depletion)
|
||||
|
||||
- 🧩 Task: Validate stacked branch readiness and make tests compatible with new medication/package semantics.
|
||||
- ✅ Decisions:
|
||||
- Confirmed stack order on `feat/topical-no-depletion-planner`: `7ebd253` -> `3954ed2` -> `e689720` -> `f9deb1b`.
|
||||
- Fixed backend in-memory test schemas (integration/planner/e2e-routes) to include new medication columns so route tests stop failing with fixture-level `500` errors.
|
||||
- Updated focused E2E selectors/assertions for schedule usage labels and planner stock wording after liquid/topical/package updates.
|
||||
- Stabilized medication lifecycle E2E edit flow by cleaning per-test medication state and using current edit-form labels.
|
||||
- 📁 Files touched:
|
||||
- `backend/src/test/integration.test.ts`
|
||||
- `backend/src/test/planner.test.ts`
|
||||
- `backend/src/test/e2e-routes.test.ts`
|
||||
- `frontend/e2e/medication-edit.spec.ts`
|
||||
- `frontend/e2e/medication-lifecycle.spec.ts`
|
||||
- `frontend/e2e/planner-data.spec.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Frontend lint still reports 12 pre-existing `noNestedTernary` warnings in `MedicationsPage.tsx` and `ReportModal.tsx` (outside this compatibility-fix scope).
|
||||
|
||||
### 2026-02-28 (package amount UX: numeric input only for tube and liquid)
|
||||
|
||||
- 🧩 Task: Remove `+/-` steppers for non-tablet package amount fields.
|
||||
- ✅ Decisions:
|
||||
- Replaced `FormNumberStepper` with a plain numeric text input for:
|
||||
- `tube` -> `Amount per tube`
|
||||
- `liquid_container` -> `Package amount`
|
||||
- Kept unit selectors read-only and fixed per domain rule:
|
||||
- `tube` unit fixed to `g`
|
||||
- `liquid_container` unit fixed to `ml`
|
||||
- Added component regression tests in mobile modal suite to validate the new input style and fixed units.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/test/components/MobileEditModal.test.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Existing unrelated `noNestedTernary` lint warnings remain in `MedicationsPage.tsx` (pre-existing scope).
|
||||
|
||||
### 2026-02-28 (test update: tube grams + liquid ml regression coverage)
|
||||
|
||||
- 🧩 Task: Review and update tests for the new package-unit behavior rules.
|
||||
- ✅ Decisions:
|
||||
- Extended existing `useMedicationForm` tests to assert `packageAmountUnit="ml"` for `liquid_container` defaults/locks.
|
||||
- Added regression test that `tube` enforces/keeps `packageAmountUnit="g"` even if UI attempts to set `ml`.
|
||||
- Added regression test that legacy `tube` records with `packageAmountUnit="ml"` are normalized to `g` during `startEdit`.
|
||||
- Refactored touched code paths to satisfy `noNestedTernary` lint in changed files.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/test/hooks/useMedicationForm.test.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Optional backend route-level validation for `tube+ml` remains open if full server-side enforcement is desired.
|
||||
|
||||
### 2026-02-28 (tube unit correction: no ml for tubes)
|
||||
|
||||
- 🧩 Task: Enforce domain rule from user feedback: tubes use `g`, not `ml`.
|
||||
- ✅ Decisions:
|
||||
- Removed `ml` choice from tube amount input in desktop and mobile edit forms (unit is fixed to `g`).
|
||||
- Added hard normalization so tube edit state and save payload always persist `packageAmountUnit="g"`.
|
||||
- Added payload guard so `liquid_container` is normalized to `packageAmountUnit="ml"`.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Optional backend validation hardening can be added later to reject historic `tube+ml` payloads server-side.
|
||||
|
||||
### 2026-02-28 (tube package UX simplification: 1 tube x 150 g)
|
||||
|
||||
- 🧩 Task: Make `tube` package fields intuitive and remove conflicting stock inputs.
|
||||
- ✅ Decisions:
|
||||
- Reworked stock UI for `packageType=tube` in desktop and mobile to show only `Tubes` + `Amount per tube` + computed total amount.
|
||||
- Removed `total/current amount` steppers for tube to avoid contradictory input combinations.
|
||||
- Save payload now normalizes tube values to a consistent amount model (`totalPills` and `looseTablets` derived from `packCount * packageAmountValue`).
|
||||
- Added i18n keys for tube-specific labels (`form.tubes`, `form.packageAmountPerTube`) in EN/DE.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- If needed, align planner/report wording to explicitly display tube multiplication format (`NxY unit`) everywhere.
|
||||
|
||||
### 2026-02-28 (start date optional placeholder consistency)
|
||||
|
||||
- 🧩 Task: Make start-date hint consistent with other optional date fields.
|
||||
- ✅ Decisions:
|
||||
- Added `common.optional` placeholder to `Medication Start Date` in desktop and mobile edit forms.
|
||||
- Kept validation behavior unchanged (start date remains optional).
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- None.
|
||||
|
||||
### 2026-02-28 (full implementation: liquid units + topical stock behavior + package amount metadata)
|
||||
|
||||
- 🧩 Task: Implement the approved suggestions end-to-end: liquid intake unit conversion, topical non-depleting stock logic, and package amount metadata.
|
||||
- ✅ Decisions:
|
||||
- Added persisted medication metadata fields `packageAmountValue` + `packageAmountUnit` (`ml|g`) in DB schema/migration and API/export/import mappings.
|
||||
- Extended intake model with `intakeUnit` (`ml|tsp|tbsp`) and conversion logic (`tsp=5 ml`, `tbsp=15 ml`) for stock calculations.
|
||||
- Enforced topical stock behavior as metadata-only depletion path in planner/reminder/frontend coverage calculations (`topical`/`tube` does not reduce stock in V1.1 behavior).
|
||||
- Added desktop+mobile parity UI for liquid intake-unit selection and package-amount metadata inputs.
|
||||
- Added EN/DE i18n keys for new fields and bumped export format to `1.3`.
|
||||
- 📁 Files touched:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `backend/src/services/reminder-scheduler.ts`
|
||||
- `backend/src/routes/export.ts`
|
||||
- `frontend/src/types/index.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/utils/schedule.ts`
|
||||
- `frontend/src/components/SharedSchedule.tsx`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Testing execution remains delegated to `@testing-manager` per governance.
|
||||
|
||||
### 2026-02-28 (topical non-measurable vs liquid measurable + tbsp conversion)
|
||||
|
||||
- 🧩 Task: Clarify that topical content should not affect stock math while liquid should, and define tablespoon conversion.
|
||||
- ✅ Decisions:
|
||||
- Updated `doku/package_types.md` so `topical` package amount is metadata-only (no depletion effect) in V1/V1.1.
|
||||
- Kept `liquid` as measurable stock with canonical `ml` deduction.
|
||||
- Added liquid intake conversion rules: `1 tsp = 5 ml`, `1 tbsp = 15 ml`.
|
||||
- Locked MedAssist conversion to medical metric convention (`tbsp=15 ml`), excluding regional culinary variants.
|
||||
- 📁 Files touched:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- If approved for implementation: add intake-unit enum (`ml|tsp|tbsp`) and conversion logic in frontend/backend.
|
||||
|
||||
### 2026-02-28 (packaging quantity unit decision for liquids/topicals)
|
||||
|
||||
- 🧩 Task: Clarify how package amount should be measured for liquid and topical medications.
|
||||
- ✅ Decisions:
|
||||
- Added explicit recommendation in `doku/package_types.md` to introduce package quantity fields: `packageAmountValue` + `packageAmountUnit` (`ml|g`).
|
||||
- Documented default mapping: `liquid_container -> ml`, `tube(topical) -> g`, with manual override to `ml` for topical liquids.
|
||||
- Clarified separation between `doseUnit`, `packageAmountUnit`, and `strengthUnit`.
|
||||
- 📁 Files touched:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- If approved, implement DB/API/frontend fields and migrations in a dedicated PR.
|
||||
|
||||
### 2026-02-28 (liquid_container regression tests implemented)
|
||||
|
||||
- 🧩 Task: Convert the checklist handoff into executable regression coverage for `liquid_container` and run targeted validation.
|
||||
- ✅ Decisions:
|
||||
- Added backend real-route regression tests in `e2e-routes.test.ts` for create/share semantics and invalid liquid/package combination rejection.
|
||||
- Added frontend hook regression tests in `useMedicationForm.test.ts` for `liquid_container` form derivation (`medicationForm=liquid`, `doseUnit=ml`) and lock behavior.
|
||||
- Fixed outdated in-memory test schema in `e2e-routes.test.ts` by adding missing medication columns (`medication_form`, `pill_form`, `lifecycle_category`, `medication_end_date`, `auto_mark_obsolete_after_end_date`) so current route inserts can be tested reliably.
|
||||
- Executed only targeted test names to isolate new behavior from unrelated legacy failures in the larger file.
|
||||
- 📁 Files touched:
|
||||
- `backend/src/test/e2e-routes.test.ts`
|
||||
- `frontend/src/test/hooks/useMedicationForm.test.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Optional next step: expand backend package/form matrix coverage beyond these focused regressions.
|
||||
|
||||
### 2026-02-28 (testing-manager handoff checklist for liquid_container)
|
||||
|
||||
- 🧩 Task: Create a concrete test handoff checklist after user confirmation.
|
||||
- ✅ Decisions:
|
||||
- Added a dedicated `@testing-manager` section to `doku/package_types.md` with backend validation cases, frontend desktop/mobile parity checks, data exchange checks, and E2E minimum scenarios.
|
||||
- Kept checklist scoped to the `liquid_container` rollout and package/form invariants.
|
||||
- 📁 Files touched:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Execute the checklist via `@testing-manager` ownership path.
|
||||
|
||||
### 2026-02-28 (liquid_container rollout)
|
||||
|
||||
- 🧩 Task: Implement dedicated liquid package type end-to-end (`liquid_container`) after user decision.
|
||||
- ✅ Decisions:
|
||||
- Added `liquid_container` to backend and frontend package type contracts.
|
||||
- Enforced compatibility: `liquid -> liquid_container`, `topical -> tube`, `capsule/tablet -> not tube/not liquid_container`.
|
||||
- Updated desktop/mobile medication forms with explicit `liquid_container` option and fixed unit/label behavior (`ml`) in planner/schedule/dashboard/detail/report flows.
|
||||
- Updated backend planner/reminder/export/refill/share logic to treat `liquid_container` as container stock semantics where applicable.
|
||||
- Updated `doku/package_types.md` constraints to reflect new canonical mapping.
|
||||
- 📁 Files touched:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `backend/src/routes/export.ts`
|
||||
- `backend/src/routes/refills.ts`
|
||||
- `backend/src/routes/share.ts`
|
||||
- `backend/src/routes/planner.ts`
|
||||
- `backend/src/services/reminder-scheduler.ts`
|
||||
- `frontend/src/types/index.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/pages/PlannerPage.tsx`
|
||||
- `frontend/src/pages/SchedulePage.tsx`
|
||||
- `frontend/src/pages/DashboardPage.tsx`
|
||||
- `frontend/src/components/MedDetailModal.tsx`
|
||||
- `frontend/src/components/ReportModal.tsx`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Run full regression via `@testing-manager` (ownership rule).
|
||||
|
||||
### 2026-02-28 (logic correction: liquid cannot be in tube)
|
||||
|
||||
- 🧩 Task: Fix package-type logic after user report that `liquid` in `tube` is invalid.
|
||||
- ✅ Decisions:
|
||||
- Backend validation changed to `topical -> tube` and `liquid != tube`.
|
||||
- UI for `packageType=tube` now only allows `topical`; the `liquid` option was removed in desktop and mobile edit flows.
|
||||
- Form defaults/derivations for `tube` now force `medicationForm=topical` with `doseUnit=units`.
|
||||
- Updated source-of-truth plan wording in `doku/package_types.md` to match corrected logic.
|
||||
- 📁 Files touched:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Re-check if a dedicated `liquid` package path should be modeled explicitly in a future master-plan phase.
|
||||
|
||||
### 2026-02-28 (package-type hardening pass implemented)
|
||||
|
||||
- 🧩 Task: Implement as much of the package-type remediation as possible before the master-plan rollout.
|
||||
- ✅ Decisions:
|
||||
- Hardened backend API validation in `medications.ts` by adding inverse compatibility rule (`capsule/tablet` cannot use `tube`).
|
||||
- Updated frontend planner and schedule views to stop pill-only wording for `tube` medications and to render form-aware units.
|
||||
- Updated backend planner/reminder notification wording to avoid pill assumptions for `tube` and use form-aware/generic unit terms.
|
||||
- Extended backend translation common keys with unit terms required for the updated notification wording.
|
||||
- 📁 Files touched:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `frontend/src/pages/PlannerPage.tsx`
|
||||
- `frontend/src/pages/SchedulePage.tsx`
|
||||
- `backend/src/routes/planner.ts`
|
||||
- `backend/src/services/reminder-scheduler.ts`
|
||||
- `backend/src/i18n/translations.ts`
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Test ownership remains with `@testing-manager`; full four-form regression coverage still pending.
|
||||
|
||||
### 2026-02-28 (package_types full-plan refresh to current implementation status)
|
||||
|
||||
- 🧩 Task: Review the full `doku/package_types.md` and bring it to the latest code-aligned state.
|
||||
- ✅ Decisions:
|
||||
- Added a dated status snapshot (`2026-02-28`) with explicit split between implemented and still-open items.
|
||||
- Removed scope ambiguity by changing V1 section into `already implemented` baseline plus `remaining work`.
|
||||
- Added explicit note that persisted lifecycle values are currently limited to `refill_when_empty|treatment_period`, while `ongoing` is runtime-derived.
|
||||
- Added progress interpretation to the 1:1 remediation section (verify-and-align for completed parts, prioritize known open gaps).
|
||||
- 📁 Files touched:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Next implementation focus remains planner/schedule/reminder wording normalization and full four-form regression coverage.
|
||||
|
||||
### 2026-02-28 (package_types plan: added 1:1 remediation execution order)
|
||||
|
||||
- 🧩 Task: Write the full executable remediation order directly into `doku/package_types.md`.
|
||||
- ✅ Decisions:
|
||||
- Added a mandatory file-by-file sequence with explicit `file -> change -> acceptance` structure.
|
||||
- Included all previously identified impacted backend, frontend, i18n, test, e2e, and documentation files.
|
||||
- Added an execution gate: skipped files require explicit technical rationale, otherwise rollout is incomplete.
|
||||
- 📁 Files touched:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Execute the remediation in code in exactly this order and track skipped items with rationale.
|
||||
|
||||
### 2026-02-28 (package_types plan made coherent + full impact inventory)
|
||||
|
||||
- 🧩 Task: Adjust `doku/package_types.md` so the plan is coherent and explicitly lists all affected code/test/doc areas.
|
||||
- ✅ Decisions:
|
||||
- Fixed top-level contradiction by documenting current container reality as `blister|bottle|tube`.
|
||||
- Added a mandatory explicit affected-file inventory across backend routes/services/schema, frontend runtime surfaces, i18n, backend tests, frontend tests, and e2e specs.
|
||||
- Kept the no-partial-rollout enforcement and linked it to concrete file groups for execution control.
|
||||
- 📁 Files touched:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Implement remediation in code according to the newly enumerated file inventory (starting with planner/schedule wording and corresponding tests).
|
||||
|
||||
### 2026-02-28 (package type plan hardening against partial implementation)
|
||||
|
||||
- 🧩 Task: Strengthen `doku/package_types.md` so package/form changes cannot be considered done when only partial surfaces are updated.
|
||||
- ✅ Decisions:
|
||||
- Added a mandatory cross-layer implementation coverage section (backend validation, backend logic, desktop+mobile parity, read views, i18n, import/export/share, tests).
|
||||
- Added explicit definition of done: all checklist areas must be updated or explicitly marked not impacted with rationale.
|
||||
- Grounded follow-up review findings with concrete gap examples still visible in code (notably planner/schedule pill-only wording paths).
|
||||
- 📁 Files touched:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Execute a dedicated cleanup pass for planner/schedule/backend planner notification wording and corresponding tests.
|
||||
|
||||
### 2026-02-28 (release-manager doc cleanup: remove app-feature example)
|
||||
|
||||
- 🧩 Task: Remove product-feature-specific text from `.github/agents/release-manager.agent.md` and keep it process-focused.
|
||||
- ✅ Decisions:
|
||||
- Replaced concrete app feature example under release notes guidance with a neutral, reusable template using placeholders.
|
||||
- Kept release process rules intact; only example content was generalized.
|
||||
- 📁 Files touched:
|
||||
- `.github/agents/release-manager.agent.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Optionally align the Breaking Changes heading example with the no-emoji rule in a separate doc cleanup pass.
|
||||
|
||||
### 2026-02-27 (dashboard overview tube unit fix)
|
||||
|
||||
- 🧩 Task: Fix dashboard medication overview showing `pill` for `tube` medications.
|
||||
- ✅ Decisions:
|
||||
- Replaced pill-based stock label in dashboard overview with tube-aware amount labels.
|
||||
- Added local dashboard helpers to render `tube` values as `ml` (liquid) or `applications` (topical).
|
||||
- Updated timeline dose/total tags in dashboard day blocks to use tube-aware units and suppress pill-weight mg details for tube.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/pages/DashboardPage.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- None.
|
||||
|
||||
### 2026-02-27 (date input placeholder casing fix)
|
||||
|
||||
- 🧩 Task: Keep `optional` placeholder text lowercase in date inputs instead of inherited uppercase styling.
|
||||
- ✅ Decisions:
|
||||
- Root cause is inherited `text-transform: uppercase` from `.form-grid label`.
|
||||
- Applied local override on `.date-input-display` (`text-transform: none`, `letter-spacing: normal`) to preserve calm, readable lowercase placeholder text.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/styles/schedule-mobile-edit.css`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- None.
|
||||
|
||||
### 2026-02-27 (tube wording consistency in report exports)
|
||||
|
||||
- 🧩 Task: Complete tube-specific wording consistency in medication report exports (text + print/PDF).
|
||||
- ✅ Decisions:
|
||||
- Added helper functions in `ReportModal` to centralize tube unit/label logic (`ml` vs `applications`).
|
||||
- Replaced pill-centric wording with amount-based wording for `tube` in current stock, total capacity label, intake schedule entries, and refill history entries.
|
||||
- Hid `Dose per pill` row for `tube` in both text and print report outputs.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/components/ReportModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Dashboard/Planner wording parity should be rechecked in a dedicated pass if product wants full app-wide amount terminology normalization for tube.
|
||||
|
||||
### 2026-02-27 (holistic package-specific UI behavior for tube)
|
||||
|
||||
- 🧩 Task: Make package tab behavior fully package-specific so `tube` does not show pill/mg-oriented fields.
|
||||
- ✅ Decisions:
|
||||
- For `tube`, relabeled stock fields from pill terminology to amount terminology.
|
||||
- Hid the pill-specific strength field (`Dose per pill`) for `tube` in desktop and mobile package tabs.
|
||||
- Adjusted total display text for `tube` to avoid `pill/pills` wording.
|
||||
- Added tube-form default unit behavior: `liquid -> ml`, `topical -> units`.
|
||||
- Added EN/DE i18n keys for amount-based labels.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Optional backend extension: distinguish volume-based depletion for liquid vs application-based depletion for topical in planner/reminder calculations.
|
||||
|
||||
### 2026-02-27 (align package_types doc with implemented tube/liquid/topical behavior)
|
||||
|
||||
- 🧩 Task: Resolve contradiction between implementation and `doku/package_types.md` technical constraints.
|
||||
- ✅ Decisions:
|
||||
- Updated constraints to reflect actual support for `packageType=blister|bottle|tube`.
|
||||
- Documented current UX split:
|
||||
- `blister`/`bottle` use `pillForm`.
|
||||
- `tube` uses `medicationForm` (`liquid`/`topical`).
|
||||
- Removed stale claim that only `blister|bottle` are supported end-to-end.
|
||||
- 📁 Files touched:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Keep docs in lockstep with model/UI changes to avoid product-level confusion.
|
||||
|
||||
### 2026-02-27 (restore liquid vs topical distinction for tube)
|
||||
|
||||
- 🧩 Task: Reintroduce meaningful liquid/topical distinction after pillForm-first simplification removed explicit tube subform choice.
|
||||
- ✅ Decisions:
|
||||
- Keep `pillForm` as primary for non-tube packages.
|
||||
- For `packageType=tube`, show `medicationForm` selector with only `liquid` and `topical` options.
|
||||
- Tube intake behavior now respects selected tube subform:
|
||||
- `liquid` -> fractional intake allowed, `usageMl` label.
|
||||
- `topical` -> integer/application-style intake, `usageApplication` label.
|
||||
- Default when switching to tube is now `liquid` unless an existing tube subform already exists.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- If stock math for liquid should be volume-precise vs application-based, add explicit unit/policy handling in backend planner/reminder calculations.
|
||||
|
||||
### 2026-02-27 (pillForm-first UX: removed medicationForm selector)
|
||||
|
||||
- 🧩 Task: Remove semantically redundant `medicationForm` vs `pillForm` user choice and make `pillForm` the primary user-facing control.
|
||||
- ✅ Decisions:
|
||||
- Removed `medicationForm` selector from desktop and mobile forms.
|
||||
- Kept `pillForm` as the user-facing form mechanic for non-tube package types.
|
||||
- Kept `packageType` explicit (`blister`, `bottle`, `tube`) and derive `medicationForm` internally on save for backend compatibility.
|
||||
- Intake behavior now keys off `packageType`/`pillForm` in UI logic (fraction rule + usage labels).
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- If liquid vs topical needs separate user-facing control later, add it only with concrete behavior differences.
|
||||
|
||||
### 2026-02-27 (remove non-functional lifecycle selector from UI)
|
||||
|
||||
- 🧩 Task: Ensure users only see options with concrete app impact.
|
||||
- ✅ Decisions:
|
||||
- Removed `lifecycleCategory` selector from desktop and mobile medication edit forms because both options currently have no distinct runtime behavior.
|
||||
- Kept persistence/internal field compatibility untouched to avoid DB/API churn in the same scope.
|
||||
- Documented that lifecycle selector remains hidden until it drives differentiated planner/reminder/stock behavior.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- If lifecycle should return as a visible control, implement real behavior differences first and then re-enable UI.
|
||||
|
||||
### 2026-02-27 (package type pivot: liquid/topical use tube, not bottle)
|
||||
|
||||
- 🧩 Task: Complete cross-layer pivot after user correction that liquid/topical must not use pill-bottle semantics.
|
||||
- ✅ Decisions:
|
||||
- Introduced/propagated dedicated `tube` package type in backend validation/export and frontend domain/UI types.
|
||||
- Enforced medication-form mapping: liquid/topical -> `tube`; capsule/tablet keep blister/bottle options.
|
||||
- Standardized stock logic so container semantics (`bottle` and `tube`) use direct loose/total capacity handling across planner/dashboard/detail/refill/report/scheduler.
|
||||
- Added missing i18n keys for tube labels in form/report contexts (EN/DE).
|
||||
- 📁 Files touched:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `backend/src/routes/export.ts`
|
||||
- `backend/src/routes/refills.ts`
|
||||
- `backend/src/routes/planner.ts`
|
||||
- `backend/src/routes/share.ts`
|
||||
- `backend/src/services/reminder-scheduler.ts`
|
||||
- `frontend/src/types/index.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/hooks/useRefill.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/pages/DashboardPage.tsx`
|
||||
- `frontend/src/pages/PlannerPage.tsx`
|
||||
- `frontend/src/components/MedDetailModal.tsx`
|
||||
- `frontend/src/components/ReportModal.tsx`
|
||||
- `frontend/src/utils/stock.ts`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Tests are owned by `@testing-manager`; no test execution was performed in this step.
|
||||
|
||||
### 2026-02-27 (rest_api_med_overview plan improved)
|
||||
|
||||
- 🧩 Task: Improve `doku/feat/rest_api_med_overview.md` based on review findings.
|
||||
- ✅ Decisions:
|
||||
- Added missing mandatory test workstream (backend, frontend, e2e).
|
||||
- Corrected rate-limit implementation target to existing architecture (`share.ts` + plugin in `backend/src/index.ts`).
|
||||
- Clarified response contract details: token format validation, `Cache-Control: no-store`, date format (`YYYY-MM-DD`), `shareStockStatus=false` nulling behavior.
|
||||
- Clarified image strategy for v1: reuse existing image filename + `/api/images/...` flow (no new share image proxy endpoint in this phase).
|
||||
- Updated effort estimate and explicitly recommended PR split due to scope size.
|
||||
- 📁 Files touched:
|
||||
- `doku/feat/rest_api_med_overview.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- If strict 500-line PR target must be enforced, execute as 3 linked PRs.
|
||||
|
||||
### 2026-02-27 (review of shared overview API plan completeness)
|
||||
|
||||
- 🧩 Task: Review `doku/feat/rest_api_med_overview.md` for quality and completeness.
|
||||
- ✅ Decisions:
|
||||
- Plan is directionally good, but not complete for implementation-readiness.
|
||||
- Critical gaps identified: missing explicit test workstream, incorrect/unclear target file for rate-limit wiring (`backend/src/app.ts` does not exist), and unresolved image-delivery contract for share overview payload.
|
||||
- Confirmed project uses `backend/src/index.ts` for Fastify plugin registration and already has `@fastify/rate-limit` registered globally.
|
||||
- Confirmed share tokens are generated via `randomBytes(8).toString("hex")` (16 hex chars), so token-format checklist is consistent with current implementation.
|
||||
- 📁 Files touched:
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Plan should be revised to include concrete backend/frontend/e2e tests and exact implementation locations before execution.
|
||||
|
||||
### 2026-02-27 (auth loading/error screen follows light/dark theme)
|
||||
|
||||
- 🧩 Task: Ensure the loading/connection screens shown before main app mount respect the selected theme.
|
||||
- ✅ Decisions:
|
||||
- Implemented theme resolution directly in `App.tsx` for pre-auth screens (`loading`, `authError`, `!authState`).
|
||||
- Read `localStorage.theme` and support `light`, `dark`, and `system` (matchMedia fallback).
|
||||
- Applied resolved theme via `data-theme` on the auth container so CSS variables immediately match the chosen theme.
|
||||
- 📁 Files touched:
|
||||
- `frontend/src/App.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- None.
|
||||
|
||||
### 2026-02-27 (testing handoff + auth/registration env dependency clarification)
|
||||
|
||||
- 🧩 Task: User requested broad Playwright improvements (auth setup fallback, planner logic assertions, lifecycle integration, retry robustness, performance timeline tests) and asked whether login/registration behavior depends on env flags.
|
||||
- ✅ Decisions:
|
||||
- Applied governance rule from `.github/skills/medassist-testing-handoff/SKILL.md`: test planning/writing/execution must be delegated to `@testing-manager`.
|
||||
- Confirmed env dependency chain:
|
||||
- Backend source of truth: `getAuthState()` in `backend/src/plugins/auth.ts`.
|
||||
- `authEnabled` comes from `AUTH_ENABLED`.
|
||||
- `registrationEnabled` is `REGISTRATION_ENABLED || !hasUsers`.
|
||||
- `formLoginEnabled` is `needsSetup || (AUTH_ENABLED && FORM_LOGIN_ENABLED)`.
|
||||
- OIDC visibility/flow depends on `OIDC_ENABLED` (+ OIDC config vars).
|
||||
- Identified why current E2E auth setup can fail in SSO-only mode: `frontend/e2e/auth.setup.ts` assumes `#username/#password` are always present.
|
||||
- 📁 Files touched:
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Hand off requested Playwright implementation scope to `@testing-manager` with concrete file-level guidance.
|
||||
|
||||
### 2026-02-27 (intake reminder fallback removal)
|
||||
|
||||
- 🧩 Task: Make intake reminders strictly per-intake and remove medication-level fallback override.
|
||||
- ✅ Decisions:
|
||||
- In `backend/src/services/intake-reminder-scheduler.ts`, removed effective reminder condition `intake.intakeRemindersEnabled || med.intakeRemindersEnabled`.
|
||||
- Reminder eligibility is now strictly `intake.intakeRemindersEnabled`.
|
||||
- Removed medication-level fallback argument when parsing intakes for reminder checks (`parseIntakesJson(..., false)`).
|
||||
- Medication prefilter now checks whether any intake has `intakeRemindersEnabled=true`.
|
||||
- 📁 Files touched:
|
||||
- `backend/src/services/intake-reminder-scheduler.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Legacy medications that relied only on medication-level reminder flag will no longer trigger reminders until intake-level flags are enabled.
|
||||
|
||||
### 2026-02-27 (remove legacy `medassist.db` leftovers)
|
||||
|
||||
- 🧩 Task: Investigate why `backend/data/medassist.db` and `data/medassist.db` existed and remove old-path remnants.
|
||||
- ✅ Decisions:
|
||||
- Verified runtime DB path is `medassist-ng.db` via `getDbPaths()` in `backend/src/db/db-utils.ts`; no production code references `medassist.db`.
|
||||
- Found remaining string references only in `backend/src/test/db-client.test.ts` mocks/assertions.
|
||||
- Updated those test references to `medassist-ng.db` and removed legacy files `backend/data/medassist.db` and `data/medassist.db` from disk.
|
||||
- 📁 Files touched:
|
||||
- `backend/src/test/db-client.test.ts`
|
||||
- `backend/data/medassist.db` (removed)
|
||||
- `data/medassist.db` (removed)
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- None; global search shows no remaining `medassist.db` code references.
|
||||
|
||||
### 2026-02-27 (split-and-ship all pending local changes)
|
||||
|
||||
- 🧩 Task: Split one large local working tree into coherent PRs and merge all to `main` end-to-end.
|
||||
@@ -363,3 +964,51 @@ Use this block for each meaningful task:
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- `frontend check` remains blocked by unrelated TypeScript errors in other files (outside MedDetailModal test scope).
|
||||
|
||||
### 2026-02-27 (package types plan decision lock)
|
||||
|
||||
- 🧩 Task: Capture user-approved decisions for lifecycle derivation and V1 scope in package type planning.
|
||||
- ✅ Decisions:
|
||||
- `ongoing` is derived from `endDate == null` and should not be stored as an explicit lifecycle value.
|
||||
- V1 form scope remains exactly 4 forms (`Capsule`, `Tablet`, `Liquid`, `Topical`) without subforms.
|
||||
- `autoMarkObsoleteAfterEndDate` default is `true`.
|
||||
- Updated wording to remove ambiguous `restore` label in lifecycle section.
|
||||
- 📁 Files touched:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Reflect these locked decisions in implementation tickets before coding starts.
|
||||
|
||||
### 2026-02-27 (implemented V1 medication form model in web app)
|
||||
|
||||
- 🧩 Task: Implement the approved V1 medication-form concept end-to-end on website (desktop + mobile) with persistence.
|
||||
- ✅ Decisions:
|
||||
- Added persisted fields: `medicationForm`, `pillForm`, `lifecycleCategory`, `medicationEndDate`, `autoMarkObsoleteAfterEndDate`.
|
||||
- Kept `ongoing` derived only: no explicit stored `ongoing` value introduced.
|
||||
- Enforced validation rules:
|
||||
- `pillForm` required for capsule/tablet medication forms.
|
||||
- fractional intake forbidden for capsule.
|
||||
- liquid/topical restricted to bottle container.
|
||||
- Implemented automatic obsolete marking during medication fetch when end date has passed and auto-mark toggle is enabled.
|
||||
- Preserved desktop/mobile parity by adding identical form controls to `MedicationsPage` and `MobileEditModal`.
|
||||
- Updated export/import format to include new metadata (`EXPORT_VERSION` bumped to `1.2`).
|
||||
- 📁 Files touched:
|
||||
- `backend/src/db/schema.ts`
|
||||
- `backend/src/db/db-utils.ts`
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `backend/src/routes/export.ts`
|
||||
- `backend/drizzle/0011_stiff_randall_flagg.sql`
|
||||
- `backend/drizzle/meta/_journal.json`
|
||||
- `backend/drizzle/meta/0011_snapshot.json`
|
||||
- `frontend/src/types/index.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `frontend/src/test/components/MobileEditModal.test.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- 🔜 Follow-up/open points:
|
||||
- Full repo-wide frontend `npm run check` still reports unrelated pre-existing e2e formatting issues outside this scope.
|
||||
|
||||
+791
@@ -28,6 +28,728 @@ For each task, add:
|
||||
|
||||
## Entries
|
||||
|
||||
### 2026-02-28 (PR #356 backend CI failure triage)
|
||||
|
||||
- **🧩 Scope**: Reproduce and fix failing `Backend Tests` check on branch `feat/package-amount-backend`.
|
||||
- **🛠️ What changed**:
|
||||
- Reproduced failure locally with `CI=true npm run test:run` in `backend` (`15` failing tests, all returning `500` from planner usage endpoint paths).
|
||||
- Root cause: missing utility export used at runtime by `POST /medications/usage`.
|
||||
- Caller: `backend/src/routes/medications.ts` (`normalizeIntakeUsageForStock(...)`)
|
||||
- Missing implementation/export in: `backend/src/utils/scheduler-utils.ts`
|
||||
- Added minimal `normalizeIntakeUsageForStock(...)` helper to return a validated finite positive numeric usage value.
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/utils/scheduler-utils.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔬 Validation run**:
|
||||
- `cd backend && CI=true npx vitest run src/test/integration.test.ts src/test/stock-semantics-parity.test.ts src/test/e2e-routes.test.ts` -> **3 files passed, 143 tests passed**
|
||||
- `cd backend && CI=true npm run test:run` -> **21 files passed, 572 tests passed**
|
||||
- **🔜 Follow-ups**:
|
||||
- None.
|
||||
|
||||
### 2026-02-28 (Stacked Branch Validation + Compatibility Fixes)
|
||||
|
||||
- **🧩 Scope**: Validate stacked commits for package amount, liquid intake units, and topical no-depletion behavior; fix test compatibility blockers.
|
||||
- **🛠️ What changed**:
|
||||
- Verified stack lineage on current branch:
|
||||
- `7ebd253` (`feat/package-amount-backend`)
|
||||
- `3954ed2` (`feat/tube-ui-simplification`)
|
||||
- `e689720` (`feat/liquid-intake-units-conversion`)
|
||||
- `f9deb1b` (`feat/topical-no-depletion-planner`)
|
||||
- Fixed backend integration fixture drift by updating in-memory `medications` schemas to current column set (including medication form + package amount fields), which removed widespread `500` failures in backend tests.
|
||||
- Updated focused Playwright specs to match current UI semantics:
|
||||
- usage label selectors now support dynamic usage labels (not pills-only)
|
||||
- lifecycle edit flow now uses robust row/action selectors and current `Commercial Name` label
|
||||
- planner stock assertion now validates blister+loose-pill breakdown format
|
||||
- Removed fixable E2E lint warnings in lifecycle spec (unused import/variable).
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/test/integration.test.ts`
|
||||
- `backend/src/test/planner.test.ts`
|
||||
- `backend/src/test/e2e-routes.test.ts`
|
||||
- `frontend/e2e/medication-edit.spec.ts`
|
||||
- `frontend/e2e/medication-lifecycle.spec.ts`
|
||||
- `frontend/e2e/planner-data.spec.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔬 Validation run**:
|
||||
- `npm run lint` (root): backend clean; frontend reports 12 pre-existing `noNestedTernary` warnings in `MedicationsPage.tsx`/`ReportModal.tsx`; no new warning introduced by this change.
|
||||
- `cd backend && CI=true npm run test:run`: **21 files passed, 572 tests passed**.
|
||||
- `cd frontend && CI=true npm run test:run`: **42 files passed, 775 tests passed**.
|
||||
- `cd frontend && PLAYWRIGHT_HTML_OPEN=never PLAYWRIGHT_WORKERS=1 npm run test:e2e -- --workers=1 e2e/medication-edit.spec.ts e2e/medication-lifecycle.spec.ts e2e/planner-data.spec.ts`: **25 passed, 0 failed**.
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional dedicated cleanup PR for remaining frontend `noNestedTernary` warnings.
|
||||
|
||||
### 2026-02-28 (UX update: no +/- for tube/liquid package amount)
|
||||
|
||||
- **🧩 Scope**: Simplify package amount input for non-tablet package types.
|
||||
- **🛠️ What changed**:
|
||||
- Replaced `+/-` stepper controls with a single numeric input field for:
|
||||
- `Tube -> Amount per tube`
|
||||
- `Liquid container -> Package amount`
|
||||
- Units remain fixed and non-editable:
|
||||
- `Tube -> g`
|
||||
- `Liquid container -> ml`
|
||||
- Added mobile modal regression tests to verify plain input behavior and fixed unit selectors.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/test/components/MobileEditModal.test.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔬 Validation run**:
|
||||
- `runTests`: `frontend/src/test/components/MobileEditModal.test.tsx` -> `59 passed, 0 failed`
|
||||
- `get_errors`: no diagnostics errors on touched files
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional: refactor pre-existing `noNestedTernary` warnings in `MedicationsPage.tsx` in a dedicated cleanup task.
|
||||
|
||||
### 2026-02-28 (Tests updated for strict tube/liquid unit behavior)
|
||||
|
||||
- **🧩 Scope**: Ensure automated tests cover the new unit rules (`tube -> g`, `liquid_container -> ml`).
|
||||
- **🛠️ What changed**:
|
||||
- Updated existing `useMedicationForm` tests to explicitly check `packageAmountUnit="ml"` for liquid-container defaults and lock behavior.
|
||||
- Added new regression test: `tube` always enforces `packageAmountUnit="g"`, even if an `ml` change is attempted.
|
||||
- Added new regression test: legacy `tube` records with `packageAmountUnit="ml"` are normalized to `g` during edit mapping.
|
||||
- Refactored touched source code for lint compliance (`noNestedTernary`) in modified files.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/test/hooks/useMedicationForm.test.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔬 Validation run**:
|
||||
- `runTests`: `frontend/src/test/hooks/useMedicationForm.test.ts` -> `25 passed, 0 failed`
|
||||
- `biome check` (focused): touched files -> clean
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional backend guard to reject `tube+ml` payloads server-side.
|
||||
|
||||
### 2026-02-28 (Domain fix: tube can no longer use `ml`)
|
||||
|
||||
- **🧩 Scope**: Enforce correct measurement semantics for tube medications.
|
||||
- **🛠️ What changed**:
|
||||
- Tube forms no longer allow selecting `ml`.
|
||||
- Tube amount unit is now fixed to `g` in desktop and mobile edit flows.
|
||||
- Existing tube records are normalized to `g` when opened in edit mode.
|
||||
- Save payload now enforces:
|
||||
- `tube -> packageAmountUnit = g`
|
||||
- `liquid_container -> packageAmountUnit = ml`
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional: add backend route-level guard to reject `tube+ml` for full server-side protection.
|
||||
|
||||
### 2026-02-28 (Tube package made simple: `1 x 150 g`)
|
||||
|
||||
- **🧩 Scope**: Remove confusing/duplicated stock inputs for tube medications.
|
||||
- **🛠️ What changed**:
|
||||
- Tube stock section was simplified in desktop and mobile forms.
|
||||
- For `Package Type = Tube`, the form now shows:
|
||||
- `Tubes`
|
||||
- `Amount per tube` (`g`/`ml`)
|
||||
- computed total amount (`Tubes * Amount per tube`)
|
||||
- Removed tube-specific `Total Amount` / `Current Amount` stepper inputs that allowed conflicting values.
|
||||
- Save logic now persists tube amounts consistently from the simple model (`packCount * packageAmountValue`).
|
||||
- Added new localized labels in EN/DE for tube-specific fields.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional: adjust read-only views to always surface the same multiplication format for tube medications.
|
||||
|
||||
### 2026-02-28 (UI consistency: start date now shows optional)
|
||||
|
||||
- **🧩 Scope**: Align date-input hint text for medication dates.
|
||||
- **🛠️ What changed**:
|
||||
- Added `optional` placeholder to `Medication Start Date` in desktop and mobile edit forms.
|
||||
- This is a display-only consistency fix; start date validation behavior is unchanged (still optional).
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- None.
|
||||
|
||||
### 2026-02-28 (Implemented: liquid intake units, topical stock rule, package amount metadata)
|
||||
|
||||
- **🧩 Scope**: Fully implement the approved behavior across backend/frontend for liquid measurements, topical stock handling, and package content metadata.
|
||||
- **🛠️ What changed**:
|
||||
- Added backend persistence + compatibility for package amount fields:
|
||||
- `packageAmountValue`
|
||||
- `packageAmountUnit` (`ml|g`)
|
||||
- Extended intake model with `intakeUnit` (`ml|tsp|tbsp`) and applied stock conversion:
|
||||
- `1 tsp = 5 ml`
|
||||
- `1 tbsp = 15 ml`
|
||||
- Updated stock/depletion logic so topical (`tube`) does not auto-deplete stock (metadata-only behavior), while liquid remains measurable and depleting.
|
||||
- Updated export/import to carry new fields and bumped export format version to `1.3`.
|
||||
- Added desktop + mobile UI controls for:
|
||||
- intake unit selection on liquid-container schedules
|
||||
- package amount metadata (`value + unit`) for tube/liquid-container
|
||||
- Updated frontend coverage logic (including shared schedule view) to match backend conversion and topical no-depletion behavior.
|
||||
- Added EN/DE translation keys for the new form labels.
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `backend/src/services/reminder-scheduler.ts`
|
||||
- `backend/src/routes/export.ts`
|
||||
- `frontend/src/types/index.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/utils/schedule.ts`
|
||||
- `frontend/src/components/SharedSchedule.tsx`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Execute test plan via `@testing-manager` (ownership rule).
|
||||
|
||||
### 2026-02-28 (Topical vs Liquid Stock Behavior Clarified)
|
||||
|
||||
- **🧩 Scope**: Define practical stock behavior for topical vs liquid medications and add intake conversion for tablespoon dosing.
|
||||
- **🛠️ What changed**:
|
||||
- Updated `doku/package_types.md` with explicit behavior split:
|
||||
- `topical`: package content is informational only (no stock depletion math in V1/V1.1)
|
||||
- `liquid`: measurable stock, always deducted in canonical `ml`
|
||||
- Added liquid intake conversion model:
|
||||
- `ml`, `tsp`, `tbsp` intake units
|
||||
- fixed conversion: `1 tsp = 5 ml`, `1 tbsp = 15 ml`
|
||||
- Added implementation guidance to use metric medical conversion (`tbsp=15 ml`) and avoid regional kitchen variants.
|
||||
- **📁 Files touched**:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Implement intake-unit enum + conversion pipeline in API/frontend if this concept is approved.
|
||||
|
||||
### 2026-02-28 (Packaging Quantity Units Clarified)
|
||||
|
||||
- **🧩 Scope**: Define how package amount should be measured for liquid and topical medications.
|
||||
- **🛠️ What changed**:
|
||||
- Added a new recommendation section in `doku/package_types.md` for explicit package quantity fields:
|
||||
- `packageAmountValue` (number)
|
||||
- `packageAmountUnit` (`ml|g`)
|
||||
- Documented practical unit mapping:
|
||||
- oral liquids (`liquid_container`) -> `ml`
|
||||
- topical cream/ointment/gel (`tube`) -> `g`
|
||||
- topical lotions/solutions -> `ml`
|
||||
- Clarified that `packageAmountUnit` is separate from `doseUnit` and `strengthUnit`.
|
||||
- **📁 Files touched**:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Implement model fields/migration/UI in a dedicated PR if this recommendation is approved.
|
||||
|
||||
### 2026-02-28 (Liquid Container Regression Tests Executed)
|
||||
|
||||
- **🧩 Scope**: Turn the `liquid_container` handoff checklist into real automated regression tests and validate the new behavior.
|
||||
- **🛠️ What changed**:
|
||||
- Added backend real-route tests in `backend/src/test/e2e-routes.test.ts` for:
|
||||
- creating a `liquid_container` medication
|
||||
- validating shared schedule stock semantics for `liquid_container`
|
||||
- rejecting invalid `liquid` + non-`liquid_container` combinations
|
||||
- Added frontend hook tests in `frontend/src/test/hooks/useMedicationForm.test.ts` for:
|
||||
- automatic form derivation when switching to `packageType=liquid_container`
|
||||
- enforcing lock behavior that keeps `medicationForm=liquid` and `doseUnit=ml`
|
||||
- Fixed the in-memory backend test schema in `e2e-routes.test.ts` so current route inserts can run against the test DB without false `500` errors.
|
||||
- Executed targeted test names for the new scenarios; all targeted tests passed.
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/test/e2e-routes.test.ts`
|
||||
- `frontend/src/test/hooks/useMedicationForm.test.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Expand the package/form matrix tests in backend route suites if broader hardening is desired.
|
||||
|
||||
### 2026-02-28 (Testing Handoff Checklist Added)
|
||||
|
||||
- **🧩 Scope**: Provide a concrete `@testing-manager` handoff for validating `liquid_container` rollout quality.
|
||||
- **🛠️ What changed**:
|
||||
- Added a dedicated testing section in `doku/package_types.md` with executable checks for:
|
||||
- backend package/form validation matrix
|
||||
- frontend desktop/mobile parity
|
||||
- planner/schedule/dashboard/detail/report unit semantics (`ml` for liquid container)
|
||||
- export/import/share/refill behavior
|
||||
- minimum E2E scenarios and pass criteria
|
||||
- Checklist is explicitly scoped to prevent regressions back to pill-only assumptions.
|
||||
- **📁 Files touched**:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Execute this checklist through `@testing-manager` per repository governance.
|
||||
|
||||
### 2026-02-28 (Dedicated Liquid Package Type Implemented)
|
||||
|
||||
- **🧩 Scope**: Implement missing package type for liquid medications using `liquid_container` and propagate it across backend/frontend.
|
||||
- **🛠️ What changed**:
|
||||
- Added `liquid_container` to API/frontend package type unions and import/export validation.
|
||||
- Enforced domain rules centrally:
|
||||
- `liquid` must use `liquid_container`
|
||||
- `topical` must use `tube`
|
||||
- `capsule/tablet` cannot use `tube` or `liquid_container`
|
||||
- Updated desktop + mobile medication edit flows to expose `liquid_container` and enforce correct form locking.
|
||||
- Updated planner/schedule/dashboard/detail/report unit rendering so liquid container values use `ml` instead of pill wording.
|
||||
- Updated refill/share/reminder/planner backend branches so container calculations include `liquid_container`.
|
||||
- Added i18n labels:
|
||||
- EN: `Liquid Container`
|
||||
- DE: `Fluessigbehaeltnis`
|
||||
- Updated `doku/package_types.md` to make `liquid_container` mapping explicit.
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `backend/src/routes/export.ts`
|
||||
- `backend/src/routes/refills.ts`
|
||||
- `backend/src/routes/share.ts`
|
||||
- `backend/src/routes/planner.ts`
|
||||
- `backend/src/services/reminder-scheduler.ts`
|
||||
- `frontend/src/types/index.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/pages/PlannerPage.tsx`
|
||||
- `frontend/src/pages/SchedulePage.tsx`
|
||||
- `frontend/src/pages/DashboardPage.tsx`
|
||||
- `frontend/src/components/MedDetailModal.tsx`
|
||||
- `frontend/src/components/ReportModal.tsx`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Test execution/triage remains delegated to `@testing-manager`.
|
||||
|
||||
### 2026-02-28 (Logic fix: Liquid is no longer allowed in Tube)
|
||||
|
||||
- **🧩 Scope**: Correct package/form logic after identifying that `liquid` in `tube` is invalid.
|
||||
- **🛠️ What changed**:
|
||||
- Backend validation rules updated:
|
||||
- `topical` must use `tube`
|
||||
- `liquid` cannot use `tube`
|
||||
- Desktop and mobile medication edit forms updated:
|
||||
- when `Package Type = Tube`, only `Topical` is selectable
|
||||
- `Liquid` option removed from tube-specific selector
|
||||
- Form-state logic updated to keep tube selections deterministic:
|
||||
- tube now forces `medicationForm=topical`
|
||||
- tube defaults to `doseUnit=units`
|
||||
- `doku/package_types.md` updated to reflect the corrected compatibility rules.
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Decide in master-plan phase how `liquid` should be modeled operationally (explicit non-tube path with full UX and stock semantics).
|
||||
|
||||
### 2026-02-28 (Package-type hardening implementation pass)
|
||||
|
||||
- **🧩 Scope**: Implement high-impact package-type fixes immediately and reduce risk before the later master-plan rollout.
|
||||
- **🛠️ What changed**:
|
||||
- Hardened backend compatibility rules in `medications.ts`:
|
||||
- `liquid/topical -> tube` (already present)
|
||||
- added inverse guard `capsule/tablet != tube`
|
||||
- Updated frontend planner/schedule wording for non-pill forms:
|
||||
- usage and totals now render form-aware units for `tube` flows
|
||||
- pill-weight helper is suppressed for tube flows
|
||||
- Updated backend planner/reminder messaging:
|
||||
- no pill-only assumption for `tube` package type
|
||||
- unit labels are now package/form aware in plain-text and email table content
|
||||
- Extended backend translation keys to support the updated unit terminology.
|
||||
- Updated `doku/package_types.md` status snapshot to reflect this implementation progress.
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `frontend/src/pages/PlannerPage.tsx`
|
||||
- `frontend/src/pages/SchedulePage.tsx`
|
||||
- `backend/src/routes/planner.ts`
|
||||
- `backend/src/services/reminder-scheduler.ts`
|
||||
- `backend/src/i18n/translations.ts`
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Full regression suite expansion for all four forms remains open and should be handled by `@testing-manager`.
|
||||
|
||||
### 2026-02-28 (Full package_types plan synchronized with current code state)
|
||||
|
||||
- **🧩 Scope**: Review the complete package-types plan and update it to the latest implementation reality.
|
||||
- **🛠️ What changed**:
|
||||
- Added a new dated status snapshot (`2026-02-28`) with clear separation of:
|
||||
- already implemented behavior
|
||||
- still-open implementation gaps
|
||||
- Updated scope section from generic "implement now" wording to:
|
||||
- `V1 baseline (already implemented)`
|
||||
- `V1 remaining work`
|
||||
- Added an explicit lifecycle storage note that current persisted values are `refill_when_empty|treatment_period`, while `ongoing` is runtime-derived.
|
||||
- Added progress interpretation for the 1:1 remediation sequence so already-delivered steps are handled as verify-and-align checks.
|
||||
- **📁 Files touched**:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Prioritize planner/schedule/reminder wording cleanup and enforce full four-form regression coverage.
|
||||
|
||||
### 2026-02-28 (1:1 remediation sequence added to package type plan)
|
||||
|
||||
- **🧩 Scope**: Add the complete execution-ready implementation order directly into `doku/package_types.md`.
|
||||
- **🛠️ What changed**:
|
||||
- Added a new section with strict `file -> exact change -> acceptance criterion` sequencing.
|
||||
- Expanded this sequence across all relevant implementation surfaces:
|
||||
- backend schema/routes/services
|
||||
- frontend runtime and parity-critical screens
|
||||
- i18n
|
||||
- backend tests, frontend tests, and e2e tests
|
||||
- documentation tracking files
|
||||
- Added an explicit execution gate so skipped files must be justified; otherwise the rollout is marked incomplete.
|
||||
- **📁 Files touched**:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Apply the remediation sequence in code and validate each acceptance criterion per file group.
|
||||
|
||||
### 2026-02-28 (Package type plan aligned and fully enumerated)
|
||||
|
||||
- **🧩 Scope**: Make `doku/package_types.md` internally consistent and explicitly enumerate all impacted areas.
|
||||
- **🛠️ What changed**:
|
||||
- Corrected plan context to current container reality (`blister|bottle|tube`) to remove ambiguity.
|
||||
- Added a full affected-file inventory grouped by:
|
||||
- backend schema/routes/services
|
||||
- frontend runtime surfaces
|
||||
- i18n files
|
||||
- backend tests
|
||||
- frontend tests
|
||||
- e2e specs
|
||||
- documentation synchronization files
|
||||
- This converts the plan into a practical implementation checklist so no affected area is accidentally skipped.
|
||||
- **📁 Files touched**:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Execute code remediation based on this inventory and close gaps in planner/schedule/backend planner messaging first.
|
||||
|
||||
### 2026-02-28 (Package type plan made implementation-safe)
|
||||
|
||||
- **🧩 Scope**: Improve `doku/package_types.md` to prevent incomplete package/form rollouts.
|
||||
- **🛠️ What changed**:
|
||||
- Added a mandatory implementation coverage checklist spanning backend, frontend desktop/mobile parity, read views, i18n, import/export/share, and tests.
|
||||
- Added a strict definition-of-done rule: package/form changes are incomplete unless all affected areas are updated (or explicitly marked not impacted with rationale).
|
||||
- This turns the plan from descriptive guidance into an execution gate against partial implementations.
|
||||
- **📁 Files touched**:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Run a targeted remediation pass for remaining pill-only wording in planner/schedule and backend planner notifications, plus matching tests.
|
||||
|
||||
### 2026-02-28 (Release manager instructions cleaned up)
|
||||
|
||||
- **🧩 Scope**: Remove app-feature text from release-manager agent instructions and keep the file process-oriented.
|
||||
- **🛠️ What changed**:
|
||||
- Replaced the concrete medication-feature release-notes example with a neutral template.
|
||||
- New template keeps the expected section structure, commit-hash usage, and full-changelog format, but avoids product-specific content.
|
||||
- **📁 Files touched**:
|
||||
- `.github/agents/release-manager.agent.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional: standardize the Breaking Changes example heading to remove emoji for full consistency with style rules.
|
||||
|
||||
### 2026-02-27 (Dashboard tube stock wording correction)
|
||||
|
||||
- **🧩 Scope**: Correct dashboard overview and timeline wording for `tube` medications.
|
||||
- **🛠️ What changed**:
|
||||
- Fixed medication overview stock cell so `tube` no longer renders as `pill/pills`.
|
||||
- Tube values now render with amount units:
|
||||
- `liquid` -> `ml`
|
||||
- `topical` -> `applications`
|
||||
- Updated timeline dose usage and total tags to use the same tube-aware units.
|
||||
- Suppressed pill-weight (`mg`) helper text for tube dose rows.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/pages/DashboardPage.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- None.
|
||||
|
||||
### 2026-02-27 (Lowercase optional placeholder in date fields)
|
||||
|
||||
- **🧩 Scope**: Make date-field placeholder text less aggressive by preventing automatic uppercase rendering.
|
||||
- **🛠️ What changed**:
|
||||
- Fixed inherited uppercase styling on custom date input display.
|
||||
- Added explicit CSS override so placeholders like `optional` render lowercase as intended.
|
||||
- Normalized letter spacing for that display text to keep the visual tone calmer.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/styles/schedule-mobile-edit.css`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- None.
|
||||
|
||||
### 2026-02-27 (Tube semantics in report exports)
|
||||
|
||||
- **🧩 Scope**: Ensure generated medication reports do not use pill-centric wording when package type is `tube`.
|
||||
- **🛠️ What changed**:
|
||||
- Updated text export (`txt`/`md`) and print/PDF report generation to use amount-based wording for `tube`.
|
||||
- Current stock in reports now uses tube units (`ml` or `applications`) instead of `pill/pills`.
|
||||
- Total capacity row label for `tube` now uses unit-aware amount wording.
|
||||
- Intake schedule rows for `tube` now render usage with amount units.
|
||||
- Refill history rows for `tube` now render added quantities with amount units.
|
||||
- `Dose per pill` row is now omitted for `tube` in report outputs.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/components/ReportModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional: run a final app-wide wording pass for dashboard/planner/schedule if complete tube terminology harmonization is desired beyond reports.
|
||||
|
||||
### 2026-02-27 (Holistic package adaptation for Tube)
|
||||
|
||||
- **🧩 Scope**: Ensure package UI reflects package semantics, especially for `tube` (liquid/topical), without pill-centric wording.
|
||||
- **🛠️ What changed**:
|
||||
- Package tab now uses amount terminology for `tube` instead of pill terminology.
|
||||
- For `tube`, removed pill-specific dose field from package tab (`Dose per pill (mg)` is no longer shown).
|
||||
- Total display for `tube` no longer appends `pill/pills` wording.
|
||||
- Added i18n labels for amount-based stock fields (EN/DE).
|
||||
- Added sensible unit defaults when choosing tube forms:
|
||||
- `liquid` -> `ml`
|
||||
- `topical` -> `units`
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional: implement distinct backend depletion math for `tube+liquid` versus `tube+topical` for full end-to-end semantic parity.
|
||||
|
||||
### 2026-02-27 (Documentation correction: Liquid/Topical + Tube constraints)
|
||||
|
||||
- **🧩 Scope**: Fix mismatch between implementation reality and `doku/package_types.md` constraints section.
|
||||
- **🛠️ What changed**:
|
||||
- Replaced outdated statement that backend/export only support `blister|bottle`.
|
||||
- Documented actual supported package types: `blister|bottle|tube`.
|
||||
- Documented current UI split clearly:
|
||||
- `blister`/`bottle`: `pillForm` (`tablet`/`capsule`)
|
||||
- `tube`: `medicationForm` (`liquid`/`topical`)
|
||||
- Clarified that export/import now include tube and related metadata.
|
||||
- **📁 Files touched**:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Continue updating this document together with any future model/UI changes in the same PR.
|
||||
|
||||
### 2026-02-27 (Tube form distinction restored: Liquid vs Topical)
|
||||
|
||||
- **🧩 Scope**: Restore meaningful separation between liquid and cream/topical while keeping the previous pillForm simplification.
|
||||
- **🛠️ What changed**:
|
||||
- Reintroduced a dedicated `Medication Form` selector only when `Package Type = Tube`.
|
||||
- Tube selector now offers exactly `Liquid` and `Topical` (no capsule/tablet overlap).
|
||||
- Kept `Pill Form` as the only form selector for `blister`/`bottle`.
|
||||
- Updated intake behavior to reflect tube form:
|
||||
- `Liquid`: fractional intake enabled and ml-oriented usage label.
|
||||
- `Topical`: application-oriented usage label and non-fractional behavior.
|
||||
- Updated form-state logic so switching to tube defaults to `Liquid` unless an existing tube form is already set.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional: introduce explicit liquid volume stock policy in backend for stronger liquid-vs-topical operational differences.
|
||||
|
||||
### 2026-02-27 (PillForm-first simplification)
|
||||
|
||||
- **🧩 Scope**: Remove semantically confusing dual form selection and align UI with meaningful domain choices.
|
||||
- **🛠️ What changed**:
|
||||
- Removed `Medication Form` selector from desktop and mobile edit forms.
|
||||
- Kept `Pill Form` as the primary form control for non-tube packages.
|
||||
- Kept explicit package selection (`blister`, `bottle`, `tube`) and use it to control whether `Pill Form` is shown.
|
||||
- Updated intake behavior logic in UI to use `packageType` + `pillForm` (fraction handling and usage label decision).
|
||||
- Backend payload remains compatible by deriving `medicationForm` internally at save time.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- If liquid/topical must become user-selectable again, add a dedicated control only together with visible behavior differences.
|
||||
|
||||
### 2026-02-27 (Removed non-functional lifecycle option from UI)
|
||||
|
||||
- **🧩 Scope**: Align medication form UX with product rule that users should only see controls that have concrete application effects.
|
||||
- **🛠️ What changed**:
|
||||
- Removed lifecycle dropdown (`Refill when empty` / `Treatment period`) from desktop medication form.
|
||||
- Removed the same lifecycle dropdown from mobile edit modal to keep desktop/mobile parity.
|
||||
- Updated package-type design doc to state lifecycle selector is intentionally hidden until lifecycle values produce distinct behavior in planner/reminder/stock logic.
|
||||
- No DB/API migration changes in this step; this is a focused UX correction to remove non-functional user choice.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Re-introduce lifecycle as visible user control only after implementing clear, user-visible behavior differences per option.
|
||||
|
||||
### 2026-02-27 (Liquid/Topical packaging correction: dedicated tube type)
|
||||
|
||||
- **🧩 Scope**: Apply user-requested domain correction so liquid/topical medications are not modeled as pill bottles and instead use a dedicated `tube` package type.
|
||||
- **🛠️ What changed**:
|
||||
- Completed backend enum/validation propagation for `tube` in medication CRUD and import/export contracts.
|
||||
- Updated backend stock/planner/share/refill/scheduler logic to treat `tube` with container semantics (same stock math branch as bottle where appropriate).
|
||||
- Updated frontend shared types and medication-form logic so liquid/topical default to `tube`.
|
||||
- Updated desktop and mobile medication edit UIs to show tube option for liquid/topical and keep bottle option for capsule/tablet.
|
||||
- Updated dashboard/planner/detail/refill/report displays and stock helpers to render/calculate tube correctly.
|
||||
- Added missing translation keys for tube labels in EN/DE (`form.packageTypeTube`, `report.docTube`).
|
||||
- Checked workspace diagnostics after edits: no compile/lint errors reported by VS Code diagnostics.
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `backend/src/routes/export.ts`
|
||||
- `backend/src/routes/refills.ts`
|
||||
- `backend/src/routes/planner.ts`
|
||||
- `backend/src/routes/share.ts`
|
||||
- `backend/src/services/reminder-scheduler.ts`
|
||||
- `frontend/src/types/index.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/hooks/useRefill.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/pages/DashboardPage.tsx`
|
||||
- `frontend/src/pages/PlannerPage.tsx`
|
||||
- `frontend/src/components/MedDetailModal.tsx`
|
||||
- `frontend/src/components/ReportModal.tsx`
|
||||
- `frontend/src/utils/stock.ts`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Delegate targeted regression test execution (frontend/unit/e2e as needed) to `@testing-manager` per repository governance.
|
||||
|
||||
### 2026-02-27 (Plan update: tokenized medication overview API)
|
||||
|
||||
- **🧩 Scope**: Improve `doku/feat/rest_api_med_overview.md` to close completeness and execution gaps.
|
||||
- **🛠️ What changed**:
|
||||
- Added a dedicated test section with concrete required coverage for backend, frontend, and e2e.
|
||||
- Fixed architecture ambiguity for rate limiting:
|
||||
- use route-level limits in `backend/src/routes/share.ts`
|
||||
- rely on already-registered plugin in `backend/src/index.ts`
|
||||
- Tightened API contract details:
|
||||
- token format validation (`^[a-f0-9]{16}$`)
|
||||
- `Cache-Control: no-store`
|
||||
- deterministic date format (`YYYY-MM-DD`)
|
||||
- explicit `shareStockStatus=false` behavior (stock-derived fields set to `null`)
|
||||
- Clarified image strategy for phase 1 (reuse existing `/api/images/...` behavior, no new share-image endpoint).
|
||||
- Updated changed-files estimate and added recommendation to split implementation into 3 PRs due to size.
|
||||
- **📁 Files touched**:
|
||||
- `doku/feat/rest_api_med_overview.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Implement in split PRs (backend -> frontend -> e2e/docs) to stay reviewable.
|
||||
|
||||
### 2026-02-27 (Review: shared overview API plan)
|
||||
|
||||
- **🧩 Scope**: Quality/completeness review of `doku/feat/rest_api_med_overview.md`.
|
||||
- **🛠️ What changed**:
|
||||
- Reviewed plan against current backend/frontend architecture and share-token implementation.
|
||||
- Found high-impact gaps to fix before implementation:
|
||||
- Missing explicit test plan (backend route tests + frontend page tests + e2e flow).
|
||||
- Ambiguous/non-existent target file for rate-limit setup (`backend/src/app.ts` in plan, but project uses `backend/src/index.ts`).
|
||||
- Image URL contract in response example is not aligned with currently visible share routes and needs explicit endpoint/policy definition.
|
||||
- Verified helpful strengths:
|
||||
- Reuse of existing `share_tokens` is consistent.
|
||||
- Token-format expectation (`hex`, 16 chars) matches current generator.
|
||||
- **📁 Files touched**:
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Update plan with concrete test tasks, precise file targets, and final image-delivery strategy before starting implementation.
|
||||
|
||||
### 2026-02-27 (Loading/Error screen theme parity)
|
||||
|
||||
- **🧩 Scope**: Make pre-auth loading and connection-error screens follow the selected light/dark theme.
|
||||
- **🛠️ What changed**:
|
||||
- Added early theme resolution in `AppRouter` for screens rendered before `AppHeader`/`useTheme` setup.
|
||||
- Supports `localStorage` values `light`, `dark`, and `system` (system resolved via `prefers-color-scheme`).
|
||||
- Applied `data-theme` on auth container during `loading`, `authError`, and `!authState` states.
|
||||
- **📁 Files touched**:
|
||||
- `frontend/src/App.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- None.
|
||||
|
||||
### 2026-02-27 (Playwright request triage + auth env dependency check)
|
||||
|
||||
- **🧩 Scope**: Assess requested Playwright improvements and clarify whether login/registration behavior is controlled by `.env` flags.
|
||||
- **🛠️ What changed**:
|
||||
- Applied repository governance: test planning/writing/execution must be delegated to `@testing-manager`.
|
||||
- Verified backend auth-state logic and confirmed env-driven behavior:
|
||||
- `AUTH_ENABLED` controls global auth mode.
|
||||
- `REGISTRATION_ENABLED` controls registration unless first-user bootstrap path (`!hasUsers`) is active.
|
||||
- `FORM_LOGIN_ENABLED` controls username/password form availability (with first-user setup override).
|
||||
- `OIDC_ENABLED` controls SSO route/button availability (with OIDC config requirements).
|
||||
- Confirmed E2E auth setup failure mode in SSO-only environments is caused by unconditional username/password field usage in setup.
|
||||
- **📁 Files touched**:
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- `@testing-manager` should implement the requested Playwright changes (auth fallback, planner calculation assertions, lifecycle integration flow, resilient retries/waits, timeline performance scenario).
|
||||
|
||||
### 2026-02-27 (Intake reminder consistency fix)
|
||||
|
||||
- **🧩 Scope**: Ensure reminders are sent only when explicitly enabled on the specific intake.
|
||||
- **🛠️ What changed**:
|
||||
- Removed medication-level reminder fallback from intake reminder scheduling.
|
||||
- Previous behavior: `intake.intakeRemindersEnabled || med.intakeRemindersEnabled` could remind disabled intakes.
|
||||
- New behavior: only `intake.intakeRemindersEnabled` qualifies an intake for reminder sending.
|
||||
- Updated prefilter logic so medications are considered only when at least one intake has reminders enabled.
|
||||
- Backend lint verified clean after change.
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/services/intake-reminder-scheduler.ts`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Existing legacy medications using only medication-level reminder flags must enable reminders at intake level to continue receiving reminders.
|
||||
|
||||
### 2026-02-27 (Legacy DB cleanup)
|
||||
|
||||
- **🧩 Scope**: Remove obsolete `medassist.db` leftovers from old storage paths and clean stale code references.
|
||||
- **🛠️ What changed**:
|
||||
- Verified active runtime DB path is `medassist-ng.db` (`backend/src/db/db-utils.ts`).
|
||||
- Searched repository for `medassist.db` and found only legacy test references in `backend/src/test/db-client.test.ts`.
|
||||
- Updated test mock/expectation paths from `medassist.db` to `medassist-ng.db`.
|
||||
- Deleted obsolete local files:
|
||||
- `backend/data/medassist.db`
|
||||
- `data/medassist.db`
|
||||
- Confirmed no remaining code references to `medassist.db`.
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/test/db-client.test.ts`
|
||||
- `backend/data/medassist.db` (removed)
|
||||
- `data/medassist.db` (removed)
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- None.
|
||||
|
||||
### 2026-02-27 (All pending local changes split and merged)
|
||||
|
||||
- **🧩 Scope**: Take the full pending local change set, split into meaningful PRs, and merge everything into `main`.
|
||||
@@ -476,3 +1198,72 @@ For each task, add:
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Resolve remaining unrelated `frontend` TypeScript errors before rerunning full `npm run check` and then the targeted MedDetailModal test command.
|
||||
|
||||
### 2026-02-27 (Package types plan decisions finalized)
|
||||
|
||||
- **🧩 Scope**: Integrate final product decisions into `doku/package_types.md` so the V1 concept is implementation-ready.
|
||||
- **🛠️ What changed**:
|
||||
- Locked lifecycle handling:
|
||||
- `ongoing` is a derived state (`endDate == null`), not a stored explicit lifecycle value.
|
||||
- Clarified precedence that `endDate` overrides `ongoing` behavior.
|
||||
- Locked V1 form scope:
|
||||
- Keep exactly 4 forms (`Capsule`, `Tablet`, `Liquid`, `Topical`).
|
||||
- No subforms in V1; users map variants like cream/gel to these forms.
|
||||
- Locked end-date behavior:
|
||||
- `autoMarkObsoleteAfterEndDate` default set to `true`.
|
||||
- Improved wording quality:
|
||||
- renamed lifecycle section title from "restore" to neutral "categorization".
|
||||
- **📁 Files touched**:
|
||||
- `doku/package_types.md`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Use these locked rules as acceptance criteria when creating implementation tasks (backend validation, frontend parity, export/import compatibility).
|
||||
|
||||
### 2026-02-27 (Website implementation: V1 medication forms + lifecycle fields)
|
||||
|
||||
- **🧩 Scope**: Implement approved package-type plan decisions directly in the web product (backend persistence + desktop/mobile UI).
|
||||
- **🛠️ What changed**:
|
||||
- Backend data model extended with new medication metadata:
|
||||
- `medicationForm` (`capsule|tablet|liquid|topical`)
|
||||
- `pillForm` (`tablet|capsule`)
|
||||
- `lifecycleCategory` (`refill_when_empty|treatment_period`)
|
||||
- `medicationEndDate`
|
||||
- `autoMarkObsoleteAfterEndDate` (default `true`)
|
||||
- Backward-compatible DB rollout implemented:
|
||||
- schema update in `backend/src/db/schema.ts`
|
||||
- alter-migration compatibility statements in `backend/src/db/db-utils.ts`
|
||||
- generated Drizzle migration: `backend/drizzle/0011_stiff_randall_flagg.sql`
|
||||
- API validation and DTO mapping updated in `backend/src/routes/medications.ts`:
|
||||
- `pillForm` required for capsule/tablet medication forms
|
||||
- fractional intake rejected for capsule
|
||||
- liquid/topical constrained to bottle container
|
||||
- end-date/start-date consistency validation
|
||||
- Auto-obsolete behavior implemented:
|
||||
- medications are auto-marked obsolete when end date is reached and `autoMarkObsoleteAfterEndDate=true`
|
||||
- Export/import now includes new metadata (`backend/src/routes/export.ts`, export format version `1.2`).
|
||||
- Frontend desktop + mobile parity implemented:
|
||||
- new form controls in `frontend/src/pages/MedicationsPage.tsx`
|
||||
- same controls in `frontend/src/components/MobileEditModal.tsx`
|
||||
- dynamic intake usage labels by form (`tablet/capsule/ml/application`)
|
||||
- capsule intake now blocks fractional values in UI
|
||||
- Frontend typing + defaults updated (`frontend/src/types/index.ts`, `frontend/src/hooks/useMedicationForm.ts`) and i18n keys added in both languages (`frontend/src/i18n/en.json`, `frontend/src/i18n/de.json`).
|
||||
- **📁 Files touched**:
|
||||
- `backend/src/db/schema.ts`
|
||||
- `backend/src/db/db-utils.ts`
|
||||
- `backend/src/routes/medications.ts`
|
||||
- `backend/src/routes/export.ts`
|
||||
- `backend/drizzle/0011_stiff_randall_flagg.sql`
|
||||
- `backend/drizzle/meta/_journal.json`
|
||||
- `backend/drizzle/meta/0011_snapshot.json`
|
||||
- `frontend/src/types/index.ts`
|
||||
- `frontend/src/hooks/useMedicationForm.ts`
|
||||
- `frontend/src/pages/MedicationsPage.tsx`
|
||||
- `frontend/src/components/MobileEditModal.tsx`
|
||||
- `frontend/src/i18n/en.json`
|
||||
- `frontend/src/i18n/de.json`
|
||||
- `frontend/src/test/components/MobileEditModal.test.tsx`
|
||||
- `doku/memory_notes.md`
|
||||
- `doku/report.md`
|
||||
- **🔜 Follow-ups**:
|
||||
- Optional: run full repo-wide frontend check after existing unrelated E2E formatting diffs are cleaned up.
|
||||
|
||||
Reference in New Issue
Block a user