- Add frontend unit tests with coverage to CI test workflow
- Add dependabot.yml for automated dependency updates (npm + GitHub Actions)
- Add backend coverage thresholds (60/65/50/60) to vitest.config.ts
- Exclude services/ and logger from coverage (untestable schedulers)
- New workflow project-auto-done.yml triggers on issue close and PR merge
- Uses GraphQL to find the project item and update Status to Done
- Handles both issues and pull requests with proper type detection
- Skips gracefully if item is not on the board or already Done
- Update release-manager.agent.md to reflect automation (manual is now fallback)
- Add critical safety rule: always verify project board status after merge
- Correct misleading claim that Closes #N auto-moves project status (it doesn't)
- Add concrete GraphQL mutation commands for moving items to Done
- Include known project field IDs for Status column
Add git pull --rebase before push to handle cases where main moved
between checkout and push (e.g., two Docker builds triggering badge
updates simultaneously). Also add concurrency group to cancel
duplicate runs.
- 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
- 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.
- 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
* 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.
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.
Allows manual triggering of the badge update workflow, useful when
the ANSI fix or other workflow-only changes need to take effect
without waiting for source code changes.
Vitest 4 outputs ANSI color codes in test results, which caused the
grep regex to fail when extracting test counts. The badge workflow
silently skipped the update, leaving stale counts in the README.
Add a sed pass to strip ANSI escape sequences before parsing.
- Remove broken isDoseFromPreviousSchedule that falsely dismissed all past doses
after any medication edit (compared dateOnlyMs < updatedAt incorrectly)
- Fix takenBy normalization in AppContext: event.takenBy (string|null) was passed
through as-is via || operator instead of being properly converted to string[]
- Fix DashboardPage: 5 locations treated dose.takenBy as single string instead of
iterating the array, causing per-person dose tracking to silently fail
- Extract isDoseDismissed and computeMissedPastDoseIds as pure testable functions
from AppContext.tsx into utils/schedule.ts
- Update SharedSchedule.tsx to use shared isDoseDismissed from utils
- Add 22 regression tests covering isDoseDismissed, computeMissedPastDoseIds,
and full dose-tracking-survives-medication-edit workflows
- Add 'fix bugs, don't test around them' rule to copilot instructions
* feat: add account deletion feature
- Add DELETE /auth/me endpoint to delete user account and all data
- Add deleteAccount() method to AuthContext
- Add Delete Account button with confirmation modal in UserProfile
- Add danger zone styling (.btn-danger, .profile-danger-zone)
- Add i18n translations for EN and DE
- Add backend tests for account deletion endpoint
- Add timeout settings to frontend vitest.config.ts
- Reduce CI timeout for frontend tests (10min -> 5min)
* fix: improve delete account section layout
- Make profile modal scrollable with max-height
- Add proper horizontal margin to danger zone
- Align delete section with form content
* fix: use ConfirmModal component for delete account dialog
- Replace inline modal with existing ConfirmModal component
- Ensures consistent button styling across all modals
- Add UI consistency rule to AGENTS.md and copilot-instructions.md
* fix: consistent styling for delete account section
- Remove warning text (users know what delete means)
- Remove border-bottom from danger zone title (section has border-top)
- Update copilot-instructions and AGENTS.md with stricter UI consistency rules
- Remove unused deleteAccountHint i18n keys
* chore: remove pre-push test hook (CI handles tests)
Tests were running twice - in pre-push hook and GitHub CI.
Removing local pre-push tests since CI provides authoritative test results.
Use 'npm test' manually before pushing if you want local feedback.
- Add test:run script to frontend package.json (consistent with backend)
- Use npm run test:run instead of npm run test -- --run
- Add timeout-minutes to prevent infinite hangs
Creates a workflow that triggers when a release is published and
automatically updates package.json versions in backend/ and frontend/
to match the release tag version.
* fix: add credentials to all fetch calls for auth cookie support
- Add credentials: include to useMedications.ts fetch calls
- Add credentials: include to MedicationsPage.tsx save function
- Add credentials: include to useSettings.ts settings update
- Add credentials: include to useShare.ts share generation
- Add credentials: include to DashboardPage.tsx reminder email
- Add credentials: include to PlannerPage.tsx usage calculation
- Make create-release workflow skip if release already exists
* fix: default to ntfy-style notifications for HTTP URLs
- Change notification logic to use plain text format by default
- Only use JSON format for known webhook services (Discord, Slack, Telegram, Gotify)
- This fixes ntfy URLs not being recognized when hostname doesn't contain 'ntfy'
* feat: highlight medication being edited
- Add blue border and background to the medication row being edited
- Show medication avatar and name in the edit form header
- Makes it easy to identify which medication is being edited when there are many
* fix: use proper URL parsing for webhook detection (CodeQL security fix)
Replace vulnerable .includes() URL checks with proper URL hostname
parsing to prevent bypass attacks (e.g., evil.com?hooks.slack.com).
Fixes CodeQL alerts #33 and #34 (js/incomplete-url-substring-sanitization)
* fix: make dismissed doses robust against schedule/timezone changes
- Store dismissedUntil date (YYYY-MM-DD) per medication instead of individual dose IDs
- Add POST /medications/dismiss-until endpoint to set dismissed date
- Add DELETE /medications/:id/dismiss-until endpoint to clear dismissed date
- Update frontend to use medication-level dismissedUntil for filtering
- Remove old dismissMissedDoses function from useDoses hook (was using dose IDs)
- Add backward-compatible ALTER TABLE migration for dismissed_until column
- Add 5 integration tests for dismiss-until functionality
- Update test schemas with new column
The old approach stored individual dose IDs which broke when schedule or timezone
settings changed (dose IDs contain timestamps). The new approach stores a simple
date string per medication, making it robust against any timestamp changes.
* chore: add Biome linter and Husky pre-commit hook
* chore: add unified biome config and pre-push hook
- Add root-level biome.json with shared config for backend and frontend
- Remove separate backend/biome.json and frontend/biome.json
- Add .husky/pre-push hook to run backend tests before push
- Update package.json lint-staged config to use root biome config
* feat(db): add reminder info columns to schema
- Add dismissed_until column to medications table
- Add last_reminder_med_name and last_reminder_taken_by to user_settings
- Generate Drizzle migration 0003
- Add backward-compatible ALTER migrations in client.ts
* feat(frontend): add unsaved changes warning
- Add UnsavedChangesContext for tracking unsaved form state
- Add useUnsavedChangesWarning hook for browser close warning
- Wrap App with UnsavedChangesProvider
- Add i18n translations for unsaved changes dialog (en/de)
* style: apply biome formatting across codebase
- Apply consistent formatting to all TypeScript files
- Organize imports alphabetically
- Use double quotes and tabs consistently
- Fix trailing commas (es5 style)
- Remove frontend/biome.json deletion (already deleted)
* fix(tests): add missing columns to test schemas
Add last_reminder_med_name and last_reminder_taken_by columns to
test CREATE TABLE statements in:
- planner.test.ts
- e2e-routes.test.ts
- integration.test.ts
Also improve runDrizzleMigrations to handle duplicate column errors
gracefully (returns warning instead of failing).
* fix(planner): add missing 'as unknown' type cast for request.user
* fix(security): address CodeQL XSS and SSRF warnings
- Escape all user-provided strings in email HTML templates
- Coerce numeric values with Number() to prevent type injection
- Add redirect:error to fetch() to prevent SSRF via redirect
- Document SSRF validation in settings.ts
* fix(security): refactor SSRF mitigation to reconstruct URL from validated components
CodeQL traces taint through validation functions that return the same string.
Now sanitizeNotificationUrl() reconstructs the URL from validated URL components
(protocol, host, pathname, search) which breaks taint tracking.
- Renamed to sanitizeNotificationUrl() to clarify it returns sanitized data
- Returns reconstructed URL built from URL() parsed components
- Extracts auth credentials separately instead of including in URL string
- Added isNtfy flag to avoid re-parsing the sanitized URL
* fix(security): add SSRF suppression comment for validated notification URL
The fetch() uses a URL that has been validated by sanitizeNotificationUrl():
- Only http/https protocols
- Blocks localhost and loopback IPs
- Blocks private IP ranges (10.x, 172.16-31.x, 192.168.x, 169.254.x)
- Blocks internal hostnames (.local, .internal, .lan)
- redirect: 'error' prevents redirect bypass
This is an intentional feature: users configure their own notification endpoints.
- Use single ./data folder for both dev and prod (removes ./backend/data)
- Update docker-compose.dev.yml to use ./data:/app/data
- Remove backend/data/ from .gitignore (only data/ needed)
- Add 'NEVER create PRs without permission' rule to copilot-instructions.md
* chore: improve release script for branch protection
- Create PR for version bump instead of direct push to main
- Wait for CI checks before merging
- Auto-merge PR and create signed tag
- Better error handling and gh CLI validation
- Works with GitHub branch protection rules
* chore(ci): create draft releases for manual release notes
Release notes should be descriptive, not auto-generated commit lists.
The workflow now creates a DRAFT release with a template.
User edits the release notes following the style guide, then publishes.
* docs: add AI release notes workflow to instructions
* chore: improve release script for branch protection
- Create PR for version bump instead of direct push to main
- Wait for CI checks before merging
- Auto-merge PR and create signed tag
- Better error handling and gh CLI validation
- Works with GitHub branch protection rules
* chore(ci): create draft releases for manual release notes
Release notes should be descriptive, not auto-generated commit lists.
The workflow now creates a DRAFT release with a template.
User edits the release notes following the style guide, then publishes.
Added prominent warning in copilot-instructions.md that every new feature
touching DB must include ALTER migrations in client.ts, not just schema.ts.
This prevents production 500 errors on existing databases.
* chore(ci): add path filters to skip CI for docs-only changes
- test.yml: Only run on changes to backend/**, frontend/**, or the workflow itself
- docker-build.yml: Only run on code/docker changes (tags still trigger unconditionally)
* docs: move screenshots below GIF and add visual indentation
- Add structured format with What's New, Features, Where to Find It
- Prefer bold feature names with inline descriptions
- Minimize emoji usage
- Add comprehensive example based on v1.3.0 release
* feat: Add Medication Refill feature with UI improvements
- Add refill functionality to medications (add packs/loose pills)
- Add refill API endpoint with history tracking
- Add refill section in edit forms (desktop & mobile)
- Add refill modal in medication detail view
- Add refill history display with expand/collapse
- Add schedule lightbox for clicking medication images
- Improve button styling with primary/info/success classes
- Move '+ New entry' button to medication list header
- Lightbox size: 50% desktop, 90% mobile
- Update selectedMed sync after stock changes
- Migrate from schema-sql.ts to Drizzle Kit migrations
* fix: Improve mobile tooltips and refill modal layout
- Center tooltips on screen for mobile devices (fixed position)
- Close tooltips automatically when scrolling on touch devices
- Use click-based tooltip activation instead of hover on mobile
- Fix refill modal buttons to display in two rows on mobile
- Add 'Keep it short' principle
- Add DO NOT include list (no technical details)
- Simplify example release notes
- Remove verbose example with implementation details