This commit introduces comprehensive observability for both backend and frontend, alongside a major UI/UX overhaul to align with modern design standards (Material 3 inspired) and improve localization.
Backend changes:
- Integrated OpenTelemetry SDK for distributed tracing.
- Added Pino for structured JSON logging with OTel instrumentation for trace/span correlation.
- Configured OTLP exporters to route traces and logs to Grafana Alloy.
- Updated docker-compose to include necessary environment variables for observability.
Frontend changes:
- Integrated Grafana Faro for Real User Monitoring (RUM), capturing Web Vitals, JS errors, and user interactions.
- Redesigned the entire UI using a new color palette and Material 3 design principles.
- Refactored component architecture (App, Home, Search, Scanner, Profile, Alerts views) for better state management and navigation.
- Improved mobile UX with a redesigned Bottom Navigation bar and Top Bar.
- Localized the interface to Spanish (es).
- Updated assets, icons, and PWA configuration (manifest, icons).
- Refactored CSS to use a centralized design token system (CSS variables).
Observability enables better debugging and performance monitoring across the entire stack.
Backend uses Jest, not Vitest — --run is unrecognized. --ci is the
Jest equivalent that disables watch mode and exits cleanly. Frontend
already uses --run --reporter=basic (Vitest) which is correct.
Verified locally: backend `npm test -- --ci` → 17/17 passing.
Co-Authored-By: Claude <noreply@anthropic.com>
The lifted screen-state refactor in PublicView broke App.test.jsx (5
failures): when no `onScreenChange` prop was passed, the click handler
called a no-op, so the screen never advanced from 'home' to 'search'
and the search input was never rendered.
Fix: make screen state uncontrolled-by-default. PublicView owns its
own `useState('home')` and uses the lifted props only when App.jsx
provides them. Same back-compat shape as before, BottomNav still
drives the screen via controlled props in the real app.
Also: vitest defaults to watch mode, which hangs the CI test step
forever. Updated docker.yaml to pass `--run --reporter=basic` so the
job exits with a real pass/fail signal.
Local verification: `npm test -- --run` → 6/6 passing.
Co-Authored-By: Claude <noreply@anthropic.com>
Replaces the floating pill nav with persistent mobile chrome that
frames scrolling content the way iOS/Android native apps do.
New components:
- TopBar: brand left, contextual action right (Admin pill for admins,
bell for saved, Login button when logged out)
- BottomNav: 4-tab fixed nav (Search, Scan [elevated center FAB],
Saved, Profile). Saved/Profile disabled when logged out, tapping
opens LoginModal
- icons.jsx: 5 stroke-based SVG icons (Search, Scan, Bell, User, Cog)
plus Logo; replaces emoji in nav chrome
Behavior changes:
- App.jsx: tracks publicScreen ('home'|'scan'|'search') so BottomNav
can drive both top-level view + inner screen
- PublicView: screen state lifted to App via props (back-compat
defaults preserved so existing call sites don't break)
- App.css: old mobile .view-switcher block hidden (replaced by
BottomNav); .app padding bumped to clear the fixed bottom bar
Desktop (>768px) unchanged — existing centered pill nav still works,
new components hidden via display:none.
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: html/body had no height/overflow constraint, so the
webview allowed rubber-band overscroll. .app used min-height: 100vh
which on iOS resolves to the larger viewport (behind URL bar), causing
layout to reflow when the bar collapses.
- index.css: html/body locked to 100% height, overflow hidden,
overscroll-behavior none; #root set to exact 100dvh
- App.css: .app height: 100dvh (was min-height: 100vh) with
overflow-y: auto + overscroll-behavior: contain so the .app
becomes the scroll container
- capacitor.config.json: ios.bounces false, contentInset never
Co-Authored-By: Claude <noreply@anthropic.com>
- Tag triggers split: ios-v* -> deploy_ios, android-v* -> deploy_android.
The blanket tag: "*" trigger that ran both platforms on every tag is gone.
- Add certificate-and-profile-installer@1 before xcode-archive@4 in
deploy_ios and combined deploy. Reads Bitrise's auto-injected
BITRISE_IOS_CODE_SIGNING_* env vars; no-op if not configured.
- Harden test_ios simulator destination to generic/platform=iOS Simulator
(was iPhone 15, OS=latest, which breaks when the runner ships a
different Xcode).
- Enable swift build cache on xcode-archive steps.
Co-Authored-By: Claude <noreply@anthropic.com>
GoogleMLKit/BarcodeScanning ~> 8.0.0 requires iOS 15.5 (per the Sept 2024
ML Kit release notes). pod install failed with "Specs satisfying the
dependency were found, but they required a higher minimum deployment
target" when the app was set to 15.0.
Co-Authored-By: Claude <noreply@anthropic.com>
- All four IPHONEOS_DEPLOYMENT_TARGET entries in project.pbxproj bumped
from 13.0 to 15.0 to match Podfile's 'platform :ios, 15.0' and
Capacitor 8's minimum. Mismatched target caused Swift compile errors
for iOS-15-only APIs in linked pods.
- Add CapacitorMlkitBarcodeScanning pod to Podfile and the package to
root package.json (already in frontend/package.json). Without this,
cap sync never linked the pod and the build failed with
'No such module CapacitorMLKitBarcodeScanning'.
Co-Authored-By: Claude <noreply@anthropic.com>
Users were wiped on container recreation because they shared the
backend_data volume with ephemeral pharmacy/medicine data. Postgres
gets its own named volume (postgres_data) that survives deploys.
- Add postgres:16-alpine service + postgres_data volume to compose
- Add pg + connect-pg-simple deps
- userDbGet/userDbRun helpers: PG when PG_URL set, SQLite fallback
- Sessions use connect-pg-simple when pgPool available
- create-admin.js: PG-aware, SQLite fallback for local dev
- push_subscriptions.user_id: plain INTEGER (cross-DB FK dropped)
- Tests unchanged — no PG_URL in test env = SQLite fallback
Set PG_PASSWORD in server .env before next deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Build context was ./backend so API/ was never copied into the image.
server.js imports ../API/index.js which resolves to /API/index.js at runtime.
Expand build context to project root and explicitly copy API/ to /API/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>