* fix(mcp): MCP RFC compliant for more strict clients
* fix(mcp): serve flat /.well-known/oauth-protected-resource for ChatGPT reconnect
Clients such as ChatGPT probe the flat well-known URL on every fresh discovery
cycle (i.e. after a full disconnect/reconnect where cached OAuth state is cleared).
The SDK's mcpAuthMetadataRouter only serves the path-based form
/.well-known/oauth-protected-resource/mcp, so the flat probe returned 404.
Without the resource metadata, ChatGPT fell back to the issuer URL as the
resource parameter (https://…/ instead of https://…/mcp). The authorize handler
then rejected it with invalid_target and redirected back to ChatGPT's callback
with an error — showing the user the TREK home page instead of the consent form.
Add an explicit GET handler for the flat URL that returns the same protected
resource metadata, so the resource URI is discovered correctly on the first probe.
* fix(mcp): fix OAuth popup blank page — SW denylist and COOP header
Service worker was intercepting /oauth/authorize navigate requests
(not in denylist), serving index.html, and React Router's catch-all
redirected to / instead of the SDK authorize handler.
Helmet's default COOP: same-origin isolated the /oauth/consent popup
from its cross-origin opener, making window.opener null and breaking
the popup-based OAuth completion signal for ChatGPT and similar clients.
* fix(ntfy): encode non-Latin-1 header values with RFC 2047 to prevent ByteString crash
Todo/trip names containing chars like → or € (and non-Latin-1 locale templates
for Czech, Chinese, Russian, etc.) caused the Fetch API to throw when setting
the ntfy Title header. Apply RFC 2047 base64 encoded-word encoding for any
header value containing chars above U+00FF; ntfy decodes this automatically.
* docs(mcp): document Cloudflare bot detection blocking ChatGPT MCP requests
Add Cloudflare WAF note to MCP-Setup and a full troubleshooting entry covering
root cause (IP reputation + UA heuristics), free-plan limitation (disable Bot
Fight Mode entirely, with explicit warning), and paid-plan WAF skip rule with
the full expression syntax and path table for all MCP/OAuth/.well-known routes.
* fix(pwa): detect upstream proxy auth challenges and recover gracefully
Behind Cloudflare Zero Trust or Pangolin, cross-origin auth redirects on
/api/* calls surface as CORS errors (error.response === undefined) that
the existing 401 interceptor never catches, leaving the PWA stuck with
network-error toasts instead of re-authenticating.
New connectivity module probes /api/health every 30s using fetch with
cache:no-store and inspects Content-Type to reliably detect whether the
server is reachable vs intercepted by an upstream proxy.
axios interceptor changes:
- On !error.response + navigator.onLine: run probeNow(); if the health
probe also fails (proxy is intercepting all requests), trigger a guarded
window.location.reload() so the edge proxy can intercept the top-level
navigation and run its auth flow (covers CF Access and Pangolin 302 mode)
- On error.response status 401 with text/html body: same reload path,
covering Pangolin header-auth extended compatibility mode which returns
401+HTML instead of a 302 redirect. TREK own 401s are always JSON so
there is no collision with the existing AUTH_REQUIRED branch.
- sessionStorage flag prevents reload loops; cleared on any successful
response so the guard resets after re-auth.
/api/health excluded from SW NetworkFirst cache (vite.config.js regex)
and Cache-Control: no-store added server-side so probes always hit the
network and cannot be served stale from the 24h api-data cache.
LoginPage caches last-known appConfig in localStorage so the SSO button
renders in OIDC+UN/PW dual mode even when the config fetch is intercepted
by the proxy. Auto-redirect to IdP skipped when config comes from cache
to avoid redirect loops while the proxy is challenging.
Fixes discussion #836.
* fix(files): add bottom-nav padding to files tab wrapper on mobile
* fix(budget): expose toolbar on mobile so users can add budget categories
* fix(pwa): unregister SW before proxy-reauth reload so Pangolin can challenge
WorkBox's NavigationRoute served the cached SPA shell on window.location.reload(),
meaning Pangolin/CF Access never saw the navigation and the app was left stuck
showing stale offline data. Unregistering the SW first lets the navigation reach
the network so the upstream proxy can run its auth flow.
Also rebuilds server/public with corrected sw.js (health excluded from
NetworkFirst, /oauth/ and /.well-known/ added to NavigationRoute denylist).
* chore: remove committed build artifacts from server/public
Dockerfile and Proxmox community script both rebuild client/dist and copy
it into server/public at build time — committed artifacts were never used.
Replace with .gitkeep and add server/public/* to .gitignore.
* chore: add build-from-sources script
- Suppress trek-stagger animation on the day list while a drag is active
so nth-child delays (0–320 ms) no longer re-fire on every hover change
- Replace sibling drop-indicator <div> injections with borderTop/borderBottom
on the target row to prevent nth-child index shifts during drag
- Dedup setDragOverDayId calls in onDragOver handlers so setState is only
invoked when the active day actually changes
- Move initTransportPositions out of getMergedItems (render path) into a
useEffect to stop mid-drag setState cascades
server/data is for runtime state (SQLite, backups, logs, tmp) — the
airports snapshot is a shipped dataset, not user data, and it being in
there forced us to poke a hole in both .dockerignore and .gitignore.
Move it to server/assets/ and drop the exceptions; service and build
script point at the new path.
Adds from/to endpoints to flight/train/cruise/car reservations with
live map rendering. Flights use geodesic arcs and a curved duration +
distance badge; train/car/cruise render as straight or geodesic lines
with endpoint markers. Airports come from an embedded OurAirports
database (~3200 airports, offline-capable); train/cruise/car locations
via Nominatim. Per-trip connection toggle sits in the day plan
sidebar, persisted in localStorage. Clicking a map endpoint opens the
existing transport detail popup. New display setting toggles endpoint
labels on the map. Migration 105 adds the reservation_endpoints table
plus needs_review flag; existing flights are backfilled from their
IATA metadata on server startup.
Introduces a fully featured notification system with three delivery
channels (in-app, email, webhook), normalized per-user/per-event/
per-channel preferences, admin-scoped notifications, scheduled trip
reminders and version update alerts.
- New notificationService.send() as the single orchestration entry point
- In-app notifications with simple/boolean/navigate types and WebSocket push
- Per-user preference matrix with normalized notification_channel_preferences table
- Admin notification preferences stored globally in app_settings
- Migration 69 normalizes legacy notification_preferences table
- Scheduler hooks for daily trip reminders and version checks
- DevNotificationsPanel for testing in dev mode
- All new tests passing, covering dispatch, preferences, migration, boolean
responses, resilience, and full API integration (NSVC, NPREF, INOTIF,
MIGR, VNOTIF, NROUTE series)
- Previous tests passing
* add test suite, mostly covers integration testing, tests are only backend side
* workflow runs the correct script
* workflow runs the correct script
* workflow runs the correct script
* unit tests incoming
* Fix multer silent rejections and error handler info leak
- Revert cb(null, false) to cb(new Error(...)) in auth.ts, collab.ts,
and files.ts so invalid uploads return an error instead of silently
dropping the file
- Error handler in app.ts now always returns 500 / "Internal server
error" instead of forwarding err.message to the client
* Use statusCode consistently for multer errors and error handler
- Error handler in app.ts reads err.statusCode to forward the correct
HTTP status while keeping the response body generic