Commit Graph

57 Commits

Author SHA1 Message Date
Daniel Volz ba0ab672b9 docs: update memory and report for multi-pr delivery (#347) 2026-02-27 01:15:40 +01:00
Daniel Volz 57c998ba09 chore: update dependabot automation and agent governance (#341)
* chore: update dependabot automation and agent governance

* chore: trigger required CI checks for governance PR
2026-02-27 01:11:05 +01:00
Daniel Volz cc22f80209 fix: align frontend types and tests for react 19 (#339) 2026-02-27 01:01:48 +01:00
Daniel Volz 19ba4bb7d2 feat: add FORM_LOGIN_ENABLED auth toggle (#334) 2026-02-27 00:48:58 +01:00
Daniel Volz f0496e8ca5 fix: remove duplicate ESC handlers causing double history.back()
AboutModal, ProfileModal, and ShareDialog each had their own
useEscapeKey hook AND were handled by the global ESC handler in
App.tsx. When ESC was pressed, both fired synchronously, calling
history.back() twice — navigating past the current page instead
of just closing the modal.

Removed the per-modal useEscapeKey calls since the global handler
in App.tsx already manages ESC priority for all modals.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-25 23:50:07 +01:00
Daniel Volz d02f16af3a fix: stabilize e2e CI and local playwright workers (#321)
* fix: stabilize e2e CI and local playwright workers

* fix(ci): apply biome formatting and import order for frontend build
2026-02-25 22:15:38 +01:00
Copilot 2a9ca39c24 Allow medications with only a generic name (no commercial name required) (#311)
* Initial plan

* feat: allow generic name only for medications (frontend changes)

- Add getMedDisplayName() helper for consistent name display
- Update validation to require either commercial or generic name
- Update all display locations to use display name fallback
- Add i18n keys for nameOrGenericRequired in en.json and de.json
- Remove required attribute from commercial name field
- Update FIELD_LIMITS.name.min from 1 to 0

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* feat: allow generic name only for medications (backend changes)

- Update Zod schema to allow empty name with cross-field refinement
- Update reminder scheduler to use name || genericName for display
- Update planner routes to match medications by display name
- Update existing tests to match new validation behavior

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

* fix: update placeholder text and fix FIELD_LIMITS test

- Remove "(optional)" from generic name placeholder in en/de
- Update types.test.ts to expect FIELD_LIMITS.name.min = 0

Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>
2026-02-25 21:29:25 +01:00
Daniel Volz 6161c14a7b fix: logo optimization, deprecated meta tag, and clipboard copy fallback (#306)
- Replace 2 MB favicon.svg (base64-PNG-in-SVG) with optimized 43 KB app-logo.png (256x256)
- Update AppHeader and AboutModal references to use new logo
- Remove SVG favicon link from index.html (PNG/ICO favicons remain)
- Fix deprecated apple-mobile-web-app-capable → mobile-web-app-capable meta tag
- Add clipboard copy fallback for non-secure contexts (LAN IP over HTTP)

Closes #303
2026-02-25 00:04:35 +01:00
Daniel Volz 96b2a0c96f feat: image upload optimization with sharp, thumbnails, and structured error codes (#304)
- Add sharp for server-side image processing (WebP conversion + thumbnails)
- New shared backend utility for image upload, optimization, and cleanup
- Return structured error codes from upload endpoints (IMAGE_TOO_LARGE, INVALID_TYPE, etc.)
- Frontend error code mapping with i18n support (EN + DE)
- MedicationAvatar tries thumbnail first, falls back to full image
- Error display in MedicationsPage, MobileEditModal, and Auth avatar upload

Closes #302
2026-02-24 23:52:59 +01:00
Daniel Volz 26475fd3d0 feat: add correlation ids and tighten frontend security headers (#299)
* feat: add correlation ids and tighten frontend security headers

* docs: remove obsolete project setup guide

* fix: restore health config flags for compatibility

* test(frontend): align auth fetch assertions with correlation headers
2026-02-24 21:21:30 +01:00
Daniel Volz ba36f67371 fix: smooth mobile edit transition and align modal validation behavior (#286)
* fix: reliable Escape key close for all modals via useEscapeKey hook

- Add useEscapeKey hook (document-level keydown listener)
- Retrofit all 12 modal/overlay components to use it
- Remove redundant overlay onKeyDown Escape handlers
- Simplify modal-content onKeyDown to plain stopPropagation
- Replace MedDetailModal's capture-phase useEffect with 3 useEscapeKey calls
- Replace SharedSchedule's inline useEffect with useEscapeKey
- Add mandatory modal rules to UI Consistency skill
- All 777 frontend + 569 backend tests pass

* fix: smooth mobile edit transition and align modal validation behavior

* fix: keep overlay keydown non-closing for Enter key

* fix: show mobile name error when validation already exists

* fix: restore app-level escape priority handling

* fix: prioritize schedule lightbox on Escape
2026-02-23 06:42:06 +01:00
Daniel Volz 2aa6b1f406 fix: prevent background scroll when any modal is open (#284)
Replace CSS-only modal-open class toggle with a shared useScrollLock
hook that uses position:fixed + scroll position save/restore. This
reliably prevents background scrolling on all browsers including
iOS Safari.

The hook supports nesting (lock counter) so stacked modals (e.g.
MedDetail → RefillModal) work correctly.

Also adds missing modal states to the scroll lock: showRefillModal,
showEditStockModal, showImageLightbox, scheduleLightboxImage.

Replaces the inline 40-line scroll lock in MobileEditModal with the
shared hook.
2026-02-22 18:40:39 +01:00
Daniel Volz 259f00e7a0 fix: unify number stepper layout and detail modal padding (#279)
Reorder stepper DOM elements (input first) and apply refill-number-stepper
class to both steppers for consistent CSS order-based layout.
Fix missing bottom padding on .med-detail-body.
2026-02-22 17:57:36 +01:00
Daniel Volz 33c1095e77 feat: add FormNumberStepper to medication edit forms (#274)
Replace plain numeric inputs with a reusable +/− stepper component in
both desktop (MedicationsPage) and mobile (MobileEditModal) edit forms.

Applied to Stock, Schedule, and Prescription tab fields. Reorder tabs
so Schedule appears before Prescription. Add responsive grid overrides
for narrow sidebar and compact schedule rows.

Fix label-hover ghost activation by placing <input> first in DOM
(CSS order restores visual [−] [value] [+] layout).

Closes #273
2026-02-22 16:49:51 +01:00
Daniel Volz 9a2d42b8b9 fix: stabilize dashboard modal and image click behavior (#267)
* feat: make medication names clickable in Dashboard dose schedule

Add click handlers to med-name-stack divs in all three dose schedule
sections (past, current/overdue, future) on DashboardPage, opening the
MedDetail modal on click.

Add early-return guards to all four modal openers in AppContext
(openMedDetail, openImageLightbox, openScheduleLightbox, openUserFilter)
to prevent duplicate pushState entries on double-click, which caused
unexpected navigation to the Medications page.

Closes #266

* fix: stabilize dashboard modal and image click handling

* fix: close medication detail on first backdrop click
2026-02-22 10:50:58 +01:00
Daniel Volz 088a6c1a05 chore: fix all Biome lint warnings and MedDetail intake bell icons (#265)
- Backend: refactor nested ternaries, remove unused imports/any types
- Frontend: fix exhaustive deps, a11y label associations, array index keys,
  empty CSS blocks, unused vars, type annotations
- MedDetail modal: fix intake schedule bell icons not rendering (use unified
  intake source with fallback), place bell inline after person name
- MedDetail modal: revert schedule rows from grid to flexbox layout

Closes #264
2026-02-22 08:52:03 +01:00
Daniel Volz afb8e5028c fix: auto-mark intakes at due time and show robot marker (#261)
* fix: auto-mark intakes at due time and show robot marker

* test: add taken_source to integration schema

* test: align e2e route schema with taken_source
2026-02-21 20:45:05 +01:00
Daniel Volz 976d7356ec feat: improve medication detail modal layout and display (#258)
Widen detail modal on desktop (711px, up from 500px) with max-width
override to beat modals-base.css specificity. Limit fullscreen mode
to actual phones (<=500px) instead of all screens <=900px. Move intake
schedule section before prescription details. Show per-intake takenBy
person and bell icon with proper warning color. Right-align time in
schedule rows. Move notes icon after label text. Replace emoji bell
icons with Lucide Bell component in SchedulePage and MobileEditModal.
Add common.on/common.off i18n keys.

Closes #254
2026-02-21 18:00:23 +01:00
Daniel Volz 943148fb49 feat: close modals with browser back button on mobile (#257)
* feat: close modals with browser back button on mobile

Create reusable useModalHistory hook that pushes history state when a
modal opens and listens for popstate to close it. Apply to ReportModal,
ClearMissedConfirm, ExportModal, ImportConfirm, and all modals using
ConfirmModal/ShareDialog/Auth/ExportModal base components. Escape key
handling was already in place for desktop.

Closes #253

* fix: update tests for renamed button labels and missing useModalHistory mock
2026-02-21 18:00:12 +01:00
Daniel Volz 94bd8bd6e8 feat: improve mobile edit modal swipe gestures and tab navigation (#256)
* feat: improve mobile edit modal swipe gestures and tab navigation

Replace React passive touch handlers with native non-passive
addEventListener via useEffect for reliable horizontal swipe blocking.
Reduce axis-lock threshold from 18-26px to 6px for more responsive
gesture detection. Remove isInteractive() guard so swipe works on
input fields. Add tab strip auto-scroll via scrollIntoView when
active tab changes. Fix vertical scrolling by changing readonly
fieldset from display:block to display:flex.

Closes #252

* fix: guard scrollIntoView for jsdom test compatibility
2026-02-21 18:00:02 +01:00
Daniel Volz 75c201cab5 fix: keep med detail stock and package values consistent (#249) 2026-02-21 15:47:44 +01:00
Daniel Volz fd3b770a81 fix: improve mobile edit modal scrolling behavior (#247) 2026-02-21 15:24:57 +01:00
Daniel Volz 612aa007aa fix: unify stock semantics across planner and scheduler (#245)
* fix: unify stock semantics across planner and scheduler

* fix: stabilize dashboard hmr and align stock helper tests
2026-02-21 15:24:53 +01:00
Daniel Volz b489e1e117 fix: keep mobile med detail actions visible while scrolling (#240) 2026-02-20 19:31:59 +01:00
Daniel Volz 052751b2ba feat: reports, timeline toggles, and stock correction improvements (#236)
* refactor(frontend): modularize styles and polish modal/ui interactions

* feat: add report workflow and timeline/settings improvements

* fix: resolve CI failures for backend typing, lint, and playwright config
2026-02-20 18:52:59 +01:00
Daniel Volz 89d565bc9d chore: fix lint errors and reduce warnings across codebase (#234)
* chore: fix lint errors and reduce warnings across codebase

- Fix noExplicitAny catches in backend routes and plugins
- Fix noNestedTernary issues in backend services
- Add keyboard event handlers for useKeyWithClickEvents in frontend
- Disable noImportantStyles rule in biome.json
- Fix formatting errors across all changed files
- Fix test file lint issues

Closes #233

* fix: restore any types in test files for TS compatibility

* fix: revert Auth.tsx dependency array changes that caused infinite re-render

* fix: null-safe user.username access in AppContext dependency array
2026-02-17 05:21:47 +01:00
Daniel Volz 779870960c fix: frontend UI polish — tooltips, planner checkbox, settings layout (#228)
- Fix mobile tooltip positioning (above icon instead of centered)
- Place planner checkbox and send-now button on same row
- Move settings tooltips beside input fields instead of overlapping
- Fix input-with-tooltip layout for narrow screens
- Add daily/everyNDays i18n keys for dose frequency display
- Fix lint formatting in page components

Closes #225
2026-02-16 21:51:51 +01:00
Daniel Volz 4b697374f6 feat: obsolete medication archiving, start date, and UI improvements (#215)
* feat: obsolete medication archiving, start date, and UI improvements

- Add soft-archive (obsolete) for medications with dedicated section and toggle
- Add medication start date field with date picker and validation
- Add obsolete/reactivate API endpoints with proper auth
- Filter obsolete meds from schedule, coverage, planner, and notifications
- Improve UserFilterModal with intake schedules, stock badges, and click-to-open
- Improve dashboard taken-by badges with per-intake bell icons
- Add Escape key support to ConfirmModal and MobileEditModal
- Fix Lightbox close button positioning near image
- Add read-only mode support for MobileEditModal
- DB migrations: 0008 (is_obsolete, obsolete_at), 0009 (medication_start_date)
- All user-facing text uses i18n keys (en + de)

* test: fix tests for obsolete medications and UI changes

- Backend: add is_obsolete, obsolete_at, medication_start_date columns to test schemas
- Backend: add test medication inserts in planner tests for active-med filtering
- Frontend: update useMedications URL to include includeObsolete param
- Frontend: fix MobileEditModal selectors and validation assertions
- Frontend: add onClearUser prop to UserFilterModal test renders
- Frontend: fix MedicationsPage and DashboardPage test assertions
2026-02-15 23:23:38 +01:00
Daniel Volz a016e45ef2 feat: frontend LOG_LEVEL support via logger utility (#209)
- Inject LOG_LEVEL at build time via Vite define (__LOG_LEVEL__, default: warn)
- Create frontend logger utility (frontend/src/utils/logger.ts) mirroring backend API
- Replace all console.error calls with log.error in MedicationsPage, AppContext, Auth
- Supports levels: silent > error > warn > info > debug

Closes #205
2026-02-14 20:28:06 +01:00
Daniel Volz 6ff0ad2745 fix: mobile modal UX improvements (delete confirm, browser-back, z-index) (#206)
- Replace browser confirm() with ConfirmModal for delete confirmation
- Add dedicated history entry for delete dialog so browser back dismisses it
- Track unsaved-changes warning source to restore correct context on cancel
- Add overlayClassName prop to ConfirmModal for nested z-index layering
- Add .nested-confirm CSS class for proper modal stacking
- Add i18n keys for delete confirmation dialog (EN + DE)

Closes #202
2026-02-14 20:17:01 +01:00
Daniel Volz 0ffab23b6d feat: add back button in medication edit header (#201) 2026-02-14 19:22:37 +01:00
Daniel Volz 8273b07231 feat: track number of prescription repeats (#193)
* feat: track prescription repeats and refill reminders

* test: align backend and frontend suites with current prescription and UI behavior

* test: update frontend and backend expectations for latest reminders and refill flow
2026-02-14 19:07:36 +01:00
Daniel Volz 1c50e9395f fix: past days UX improvements and clear missed logic (#152)
- Render past days above 'Show past days' toggle when expanded
- Auto-scroll to today when expanding past days
- Remove blue hover color from past day dividers (use opacity instead)
- Fix 'All taken' logic: green only for manually taken doses
- Yellow styling stays for days with non-taken doses (even after dismissal)
- Warning icon disappears after 'Clear missed' (dismissed doses not counted)
2026-02-10 16:42:23 +01:00
Daniel Volz 749e92b135 fix: bottle total capacity backward compatibility (#149)
* 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
2026-02-09 20:59:30 +01:00
Daniel Volz 3ec1460c4e feat: frontend improvements - shared schedule, bottle type, settings UI, planner notifications (#146)
- 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
2026-02-09 19:33:54 +01:00
Daniel Volz 3de1b2ef0c fix: UI polish for intake form, dashboard cards, and schedule (#142)
- 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
2026-02-08 22:13:52 +01:00
Daniel Volz 8c5deed4c2 feat: theme dropdown with system preference and comprehensive bottle-type fixes (#138)
- 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
2026-02-08 20:32:40 +01:00
Daniel Volz e0c5eb4bf3 feat: simplify About modal with single version link to GitHub release (#123)
- 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
2026-02-08 13:09:33 +01:00
Daniel Volz 01deea1fa0 fix: dose tracking broken for per-intake takenBy and after medication edits (#100)
- 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
2026-02-06 21:55:21 +01:00
Daniel Volz 571d94bf7e feat: Add package type support and per-intake takenBy (#89)
## Package Type Feature
- Add 'blister' and 'bottle' package types for medications
- Bottle type uses totalPills for capacity and looseTablets for current stock
- Blister type continues to use packCount/blistersPerPack/pillsPerBlister
- Add doseUnit field for flexible dosing (mg, ml, IU, etc.)
- Full UI support in medication form and detail modal

## Per-Intake TakenBy
- Move takenBy from medication level to individual intakes
- Each intake schedule can now be assigned to a different person
- Update scheduler-utils to handle per-intake takenBy
- Update SharedSchedule to filter by per-intake takenBy
- Backward compatible with existing medication data

## UI Improvements
- Add PasswordInput component with show/hide toggle
- Centralize stockThresholds in AppContext for consistent status display
- Fix SharedSchedule sync issues with per-intake takenBy
- Improve mobile editing experience

## Technical
- Add migrations 0004 and 0005 for schema changes
- Update all relevant tests (1064 tests passing)
- Maintain backward compatibility with ALTER migrations
2026-01-31 23:49:11 +01:00
Daniel Volz ac4b8151e4 fix: filter out doses from previous schedules in SharedSchedule (#88)
- Add updatedAt field to share API response
- Add isDoseFromPreviousSchedule check in SharedSchedule
- Don't count doses scheduled before medication update as missed
- Syncs SharedSchedule behavior with main app's AppContext logic
2026-01-31 08:54:09 +01:00
Daniel Volz 99ef5bd622 feat: streamline dashboard UI and improve refill reminder (#86)
- Hide Reorder Reminder card when reminders are enabled (avoids redundancy with Reminder Bar)
- Show all low stock medications in Reminder Bar instead of just the next one
- Rename 'Reorder' to 'Refill' throughout the app
- Make medication names clickable in Refill Reminder card (opens detail modal)
- Add daysLeft display for each low stock medication
- Update translations (EN + DE)
2026-01-30 22:21:05 +01:00
Daniel Volz 1dcd333fde feat: add account deletion feature (#85)
* 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.
2026-01-30 21:13:11 +01:00
Daniel Volz aed0b20875 refactor: deduplicate formatters and improve test mocks (#81)
- Consolidate duplicate date formatting utilities
- Use shared formatters across backend and frontend
- Clean up test mocks to use consistent test data
- Remove redundant formatting functions
2026-01-30 18:37:24 +01:00
Daniel Volz e725700d10 fix: only count missed doses scheduled after medication update (#79)
When medication intake times change, dose IDs change (they include
timestamps). Previously, this caused all past doses to appear as
'missed' because the old 'taken' markers no longer matched.

Now doses are only counted as 'missed' if they were scheduled AFTER
the medication's last update (updatedAt). This means:
- Legitimately missed doses still show as missed (e.g., yesterday's
  dose not taken)
- Doses from before a schedule change are NOT counted as missed
  (they were from a previous schedule configuration)

Changes:
- AppContext: Add isDoseFromPreviousSchedule helper
- SchedulePage: Use context's missedPastDoseIds instead of local calc
- Update tests to include missedPastDoseIds in mocks
2026-01-25 20:45:11 +01:00
Daniel Volz 01283ebd15 chore: rename MedAssist to MedAssist-ng in all frontend UI (#74)
Update all visible text from 'MedAssist' to 'MedAssist-ng':
- Auth page titles (login, register)
- Loading/error/initializing states
- SharedSchedule page (loading, expired, error, footer)
- AboutModal fallback text
- i18n strings for export file validation (EN/DE)
- Related test expectations
2026-01-25 19:32:17 +01:00
Daniel Volz 18bcb96869 fix: add automatic retry for auth state fetch on connection errors (#73)
When the server is restarting (e.g., during tsx watch hot reload), the
initial auth state fetch may fail. This change adds automatic retry
logic (up to 3 attempts with 1s delay) to handle transient connection
errors gracefully instead of immediately showing the error screen.
2026-01-25 19:16:24 +01:00
Daniel Volz cab0fcbba7 feat: mobile UI improvements, biome linting, and reminder info display (#71)
* 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.
2026-01-25 18:01:35 +01:00
Daniel Volz 8e2fd0a761 chore: release v1.5.0 (#67)
* chore: release v1.4.0

* feat: timezone-aware locale formatting

- Add TIMEZONE_TO_REGION map for 50+ timezones worldwide
- Combine app language with timezone region (e.g., en + Europe/Berlin → en-DE)
- Fix times displaying in wrong timezone (treated as UTC instead of local)
- Add parseLocalDateTime() to handle ISO strings without UTC conversion
- Users now get regional formatting (24h time, local date format) regardless of app language
- Swedish user with en-SE locale now gets yyyy-mm-dd format and 24h time
- German user with en-DE locale gets dd.mm.yyyy format and 24h time
- Add missing i18n translation key 'lastSent'
- Update all getSystemLocale() calls to pass app language parameter

* chore: release v1.5.0

* fix: timezone-independent test for CI (use 14:00 instead of 22:00)

* fix: make timezone test independent of server timezone
2026-01-23 21:42:57 +01:00
Daniel Volz 8718311876 refactor(frontend): modularize App.tsx into components, pages, hooks, and context (#60)
- Extract App.tsx from 764 lines to ~404 lines
- Create reusable components: MedDetailModal, MobileEditModal, ShareDialog, etc.
- Add AppContext for global state management
- Split pages: DashboardPage, MedicationsPage, SchedulePage, SettingsPage, PlannerPage
- Create custom hooks: useAuth, useMedications, useSettings, useDoses, useSchedule
- Add utility functions in separate modules
- Fix stock status logic (>30 days = green/normal)
- Fix reminder threshold calculation (use reminderDaysBefore not lowStockDays)
- Fix takenBy validation (send [] instead of null)
- Fix datetime format for blister start times (add Z suffix)
- Style 'All OK' status as green/bold

BREAKING: None - all existing functionality preserved
2026-01-22 05:38:34 +01:00