* fix: bottle total capacity shows dash for old medications
Old medications created before the totalPills column was added had
totalPills=null. This caused two issues:
1. MedDetailModal showed '—' instead of the actual capacity in the
Package Details section (while the Stock section showed correct values)
2. Edit form showed an empty Total Capacity field on mobile
Fix: Fall back to packageSize (looseTablets for bottles) when totalPills
is null, matching the behavior already used in MedicationsPage and the
stock display section.
Added test for backward compatibility scenario.
* chore: retrigger CI
When the scheduler missed the exact notification minute (due to system sleep,
high load, or GC pauses), the advance reminder was permanently lost. A dead zone
existed between the notify time and the intake time where neither advance nor
missed-intake logic would trigger.
Changes:
- getUpcomingIntakes now catches up intakes where the notify window passed but
the intake time is still in the future
- Seeding logic sends a catch-up notification for recently missed intakes
(within grace period) instead of silently seeding state
- Added 4 tests covering catch-up scenarios
- Rewrite SharedSchedule to match DashboardPage rendering with time-based consumption
- Add bottle package type support across all views (MedDetail, Refill, Planner, Dashboard)
- Redesign settings page with colored threshold chips, validation, and stock reminder display
- Add shareStockStatus toggle and send manual reminder button
- Pill/pills singular/plural consistency across all views
- Planner send notification via push (Shoutrrr) in addition to email
- Stock overflow warning and past-missed day styling
- Update README: bottles in Smart Inventory, push in Trip Planner, new ENV section
- 708 passing frontend tests including new coverage for all changes
- Separate stock/intake reminder tracking in DB with dedicated columns
- Add shareStockStatus setting to control stock visibility on shared links
- Rewrite planner notification to support both email and Shoutrrr push
- Add push notification footer text for intake and stock reminders
- New DB migrations: stock_reminder_tracking (0006), share_stock_status (0007)
- Update backend i18n with demandCalculator section and critically low text
- Add 514 passing backend tests including new coverage for all changes
The Demand Calculator used max(now, start) as the effective planner start,
which caused asymmetric counting when the current time fell between morning
and evening doses. For example, at 15:00 a medication with 07:00+20:00
intakes over 3 days showed 5 pills (2+3) instead of 6 (3+3) because the
morning dose on the start day was skipped while the evening was counted.
Changes:
- Use the user-selected start date directly instead of max(now, start)
- Optimize calculateUsageInRange to skip ahead to the relevant range
instead of iterating from the original blister start date
- Add regression tests for asymmetric counting and blister-before-range
- Intake form: replace remind checkbox with bell icon + toggle switch
- Intake form: smart takenBy dropdown based on medication's people
- Dashboard: hide DETAILS row for pill bottles on mobile cards
- Dashboard: use status-chip with icons in schedule view (past/today/future)
- Dashboard: reduce spacing between icons and status chips on mobile
- MedDetailModal: show package type in PACKAGE DETAILS heading
- PlannerPage: show dash for bottle blisters column
- Shorten Pill Bottle label in EN/DE translations
- Update related tests
- Add startup logger (utils/logger.ts) with LOG_LEVEL support
- Add ServiceLogger type for scheduler functions
- Replace all console.log calls with leveled log methods
- Downgrade verbose scheduler info logs to debug level
- Remove unnecessary console.log in auth plugin
- Replace dark/light toggle with Light/Dark/System dropdown menu
- System theme follows OS prefers-color-scheme setting
- Apply theme dropdown to shared schedule page
- Fix 7 packageType (bottle) bugs across stock calc, share, refills, export/import
- Fix planner bottle-type stock calculation and display
- Fix dailyRate double-counting with per-intake takenBy
- Fix About modal update check stale caching
- Fix intake reminder past-intake seeding and push title
- Fix phantom DB path in drizzle.config.ts
- Fix mobile dose field visibility
- Make medication name clickable in dashboard reminder bar
- Improve planner checkbox UX with inline tooltip
- Add 20+ new tests covering all fixes
Manual mode: Use takenAt timestamp instead of dose date-only comparison
to correctly distinguish doses taken before vs after stock correction
on the same day. Add polling race condition guard (mutationInFlightRef)
so Take/Undo immediately reflects in dashboard stock.
Automatic mode: Grid-align effectiveStart to the medication schedule
and use hybrid consumed calculation (time-based + early-taken doses)
for accurate stock counting.
- Tag builds now also set 'latest' Docker tag (fixes race condition where
main-push build could overwrite latest with older version)
- Remove duplicate release.yml (create-release job in docker-build.yml
already handles GitHub releases)
- Remove redundant version-bump.yml (release.sh already bumps versions
in the release PR)
- Change update-test-badges.yml trigger to workflow_run after successful
docker-build (prevents parallel execution and ensures correct ordering)
- Update agent instructions and CI documentation to reflect changes
- Fix bottle type: submitStockCorrection used blister formula for baseTotal
but getMedTotal uses only looseTablets for bottles. Now uses getPackageSize()
which handles both types correctly.
- Fix manual mode: same-day taken doses were counted as consumed after a stock
correction (>= comparison with date-only timestamps). Changed to > so doses
on the correction day are excluded.
- Add agent instruction: only release-manager may create PRs/push/merge.
Tests are already guaranteed by branch protection (test.yml must pass
before PR can be merged to main). Running them again in docker-build.yml
was redundant and slowed down image builds.
This reduces test runs from 3x to 2x per code change:
- test.yml on PR (required by branch protection)
- update-test-badges.yml on main push (needed for badge counts)
Docker image builds now start immediately after merge.
Revert Dockerfile to use /tmp redirect for envsubst output, so the image
works regardless of docker-compose.yml tmpfs configuration. Removes the
uid=101,gid=101 requirement from compose that was a breaking change.
Remove Dockerfile /tmp workaround hacks (NGINX_ENVSUBST_OUTPUT_DIR and sed).
Use tmpfs with uid=101,gid=101 in docker-compose.yml instead, so the
nginx user can write to /etc/nginx/conf.d directly under read_only: true.
- Replace separate Frontend/Backend versions with single app version
- Version is now a clickable link to the GitHub release page
- Replace stopwatch SVG with actual app logo (favicon.svg)
- Fix update check UX: previous result stays visible during re-check
- Add 1s minimum delay for update check spinner visibility
- Reserve space for update result to prevent modal jumping
- Remove unused i18n keys (frontend/backend)
- Update release-manager docs with version link info
Redirect NGINX_ENVSUBST_OUTPUT_DIR to /tmp and update nginx.conf include
path so envsubst works with read_only: true in docker-compose.
Add tmpfs mount for /etc/nginx/conf.d for additional write layer.
* fix: badge workflow commits directly instead of creating PRs
Replace peter-evans/create-pull-request with direct git push.
Removes need for pull-requests:write permission and the repo setting
'Allow GitHub Actions to create pull requests'.
Uses [skip ci] in commit message to avoid triggering itself.
* chore: trigger CI
Document that both backend/package.json and frontend/package.json
must be updated before tagging a release, since the About modal
reads versions from these files.
Fixes#96
- nginx.conf converted to template processed by envsubst at container start
- BACKEND_URL env var (default: backend:3000) replaces hardcoded container name
- Docker DNS resolver used for dynamic upstream resolution
- Dockerfile copies nginx.conf as template to /etc/nginx/templates/
This prevents frontend breakage when users customize container names
in their docker-compose.yml.
- getDataDir() now detects monorepo by checking for ../docker-compose.yml
- DATA_DIR env var removed from .env and .env.example (no longer needed for local dev)
- Docker compose files explicitly set DATA_DIR=/app/data for containers
- Updated tests for monorepo detection logic
Add DATA_DIR env var support to configure the data directory path.
All hardcoded resolve(cwd, 'data') paths now use a central getDataDir()
function from db-utils.ts that checks DATA_DIR first, falling back to
resolve(cwd, 'data').
This prevents local dev (cd backend && npm run dev) from creating a
separate backend/data/ directory instead of using the root data/ folder.
Changes:
- Add getDataDir() to db-utils.ts as single source of truth
- Update all 8 source files that reference the data directory
- Add dotenv fallback to ../.env for local dev from backend/
- Add DATA_DIR documentation to .env.example
- Add 7 new tests for getDataDir and getDbPaths with DATA_DIR
- 493 tests pass, TypeScript clean
Two bugs in the backend medications route:
1. Planner /medications/usage had the same +1 phantom consumption bug
that was fixed in the frontend (PR #109). After a stock correction,
effectiveStart was set to max(blisterStart, correctionCutoff) instead
of correctionCutoff + period, causing 1 dose to be immediately
counted as consumed.
2. PUT /medications/:id did not reset stockAdjustment when stock fields
(packCount, blistersPerPack, pillsPerBlister, looseTablets) changed.
If a user edited stock values to correct their inventory, the old
stockAdjustment offset was preserved, resulting in wrong totals.
Added 4 tests covering both scenarios.
Extract DB utility functions (buildDbUrl, getDbPaths, ensureDataDirectory,
runAlterMigrations, etc.) from client.ts into db-utils.ts.
client.ts contained top-level initialization code (ensureDataDirectory,
createClient) that ran on every import. database.test.ts imported utility
functions from client.ts, which triggered the initialization as a side
effect — creating backend/data/ with a .write-test file and
medassist-ng.db every time tests ran.
Now database.test.ts imports from db-utils.ts (side-effect-free), and
client.ts re-exports everything for backward compatibility.
test.yml: Use dorny/paths-filter to detect changed paths. Backend
tests only run when backend/**, biome.json, or the workflow itself
changes. Frontend build only runs when frontend/**, biome.json, or
the workflow changes. Jobs skipped via job-level 'if:' are treated
as passed by GitHub required checks.
codeql.yml: Only run on push/PR when JS/TS source files, package
files, or CodeQL config changes. Weekly schedule and manual dispatch
remain unfiltered.
Create dedicated GitHub Copilot agent for release management with
4 tasks: branch/PR workflow, version determination, release execution,
and release notes writing.
Move release-specific instructions (workflow, release notes format,
breaking changes) from copilot-instructions.md to the agent file
to keep concerns separated.
After correcting medication stock, the coverage calculation immediately
counted 1 dose as consumed (due to +1 in occurrences formula), which
neutralized small corrections like +1 pill.
Fix: start consumption counting from stockCorrectionCutoff + period
(the next scheduled dose) instead of from the correction time itself.
Added 3 frontend tests for stock correction scenarios and 6 backend
e2e tests for the PATCH /medications/:id/stock-adjustment endpoint.
The groupedSchedule useMemo used slice(0, 2000) to limit events. With daily
medications having start dates far in the past, thousands of past events would
fill all 2000 slots, pushing today and future events completely out of the
display. This caused the past schedule to only show weekly medications (fewer
events) while daily medications appeared missing.
Replace the fixed count limit with a time-based window: only past events
within the scheduleDays window (30/90/180 days) are included. All today and
future events are always included regardless.
Coverage calculations are not affected as they use schedule.events directly.