iOS Safari has no beforeinstallprompt API, so the portal's Install
button (?install=1) silently did nothing there. IosInstallHint now
shows Share -> Add to Home Screen steps on Safari, or a prompt to
switch to Safari first if opened in another iOS browser/in-app
webview (those can't install PWAs on iOS at all).
Also added apple-mobile-web-app-title + apple-touch-icon so the
home screen icon isn't a page screenshot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two compounding issues were kicking installed/standalone maintenance
PWA sessions out into the portal's framed browser view on re-login:
- manifest scope was the app's own base path instead of "/", so any
same-origin navigation outside it (like the old redirect to
/login) dropped the standalone window into a regular browser tab
- AuthGate unconditionally hard-navigated window.top to the central
/login on session expiry, even when not embedded in the portal
iframe, which is exactly the navigation the doc warns against
AuthGate now only bounces to central login when actually embedded
(and passes ?from= so it returns to this app afterwards); standalone
or directly-opened tabs get an in-app login form and never navigate
away. Also wired up the previously-dead inactivity auto-logout timer
(disabled for installed PWAs, configurable per device otherwise).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TaskModal: grey placeholder thumb with spinning loader appears in the photo grid immediately on file pick, replaced by the real thumb once upload+resize completes.
NewTaskModal: submit button shows spinning icon with 'Creating task…' then 'Uploading photo N of M…' instead of just greying out.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Photos open in a full-screen lightbox overlay instead of navigating to a new tab (fixes PWA back-button issue)
- DB migration adds photo_id to task_events; photo upload logs the reference so activity timeline can show a clickable thumbnail for each 'Photo added' event
- Edit and Delete buttons moved to a quiet ghost-button footer at the bottom-right of the modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Buffer upload → auto-rotate (EXIF) → resize to 1800px max → JPEG 82% progressive. Typical phone photo drops from 4-8 MB to ~200-400 KB. sharp prebuilt binaries include libvips so no Alpine package changes needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Edit button (update cap): inline form for title, description, priority, unusable flag, due date. Delete button (admin only): hard delete with confirm step, cascades photos/events and cleans files from disk. Backend adds DELETE /api/tasks/:id route.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Status and Filters dropdowns are back as expanding chips. Sort chip sits inline to the right of the two accordion headers as a peer element in a flex filter-row.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Status and filter sections now always visible in one scrollable chip-bar. Sort chip cycles through: Priority high→low, Priority low→high, Age oldest→newest, Age newest→oldest.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a task is created or reassigned to a staff member, a push notification
is sent to any devices they have subscribed on. Keys are auto-generated on
first boot and stored in the config table (no manual VAPID setup needed).
- Backend: web-push dep, push_subscriptions table, lib/push.js (VAPID +
send), routes/push.js (vapid-key / subscribe / unsubscribe endpoints),
push notify wired into task-core createTask and tasks PATCH reassignment
- Frontend: switched vite-plugin-pwa to injectManifest strategy, custom
sw.js handles precache + push event + notificationclick, usePushSubscription
hook requests permission and registers subscription on login
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both chip bars now start collapsed with a summary header showing
the active selection (e.g. "In Progress" / "Rooms view · Mine only").
Tap the header to expand and change filters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire up vite-plugin-pwa so Docker builds generate the web manifest
and service worker (was missing, causing installs to show portal icon).
Adds UpdateBanner with version-based polling via health endpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- nginx try_files: remove $uri/ so the Vite assets/ build directory
doesn't shadow the /assets React route and trigger a 403
- Remove VitePWA plugin (leftover from scaffold, not needed for internal app)
- Strip PWA-specific nginx location blocks (manifest, sw.js, registerSW.js)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Layers chip in the second chip bar groups tasks by location alphabetically
with section headers. Location name is suppressed from task card meta when
grouping is active to avoid redundancy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A confirmed booking for today that hasn't checked in yet is accessible —
staff can get in before the guest arrives. Previously it was lumped with
in-room guests and greyed out.
Backend: classifyBookingStatus() splits NewBook 'staying' bookings by
status string (normalised). Statuses containing 'arriv', 'inhouse' or
'checkedin' → occupied; everything else → arriving. /api/occupancy now
returns both occupied_site_ids and arriving_site_ids.
Frontend:
- Occupied rooms: greyed out + 'Occupied' badge (guest physically in room)
- Arriving today: normal display + amber 'Arrival today' badge (access now,
prioritise before guest checks in)
- Chip label shows 'Rooms (X free • Y arriving • Z occupied)' breakdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- #5: SearchSelect now shows the area/category as a muted right-aligned
label on every dropdown item — visible at a glance whether a result is
a Room, Kitchen, Garden etc. Typing the category name already filtered
correctly; the label makes it clear without needing to read the group header.
Applies to both the new-task location picker and the asset location picker.
- #6: Rename 'Rooms view' chip to 'Rooms Accessible View' to better convey
that it shows which rooms staff can actually access right now.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- #2#5: SearchSelect now splits query into words — each word must appear
anywhere in label or group, so '101' matches 'Room 101' and 'room 101'
matches too (multi-word, order-independent)
- #6: Rooms-view chip replaces 'Unoccupied rooms only'. When active:
filters task list to rooms category only, fetches live occupancy, and
shows occupied-room tasks greyed out with an 'Occupied' badge — so small
jobs (bulb changes etc.) are still visible without being buried. Category
chips hide while rooms-view is on to avoid conflict.
- #8: Rooms-view chip shows total rooms count + free count
e.g. 'Rooms (5 • 3 free)'. Category chip counts stay as-is (full
count) and hide when rooms-view is active.
- #3/#4 follow-up: Add pencil edit button on every asset table row so
users can edit an asset directly without opening the detail modal first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- #2#5: Replace plain <select> location pickers in NewTask and Assets with
SearchSelect combobox — full-text search across name and category group
- #3: Add "Unknown" checkbox on asset install date — disables the field and
saves null instead of leaving an ambiguous blank
- #4: Add inline "Add service task" form in asset detail modal — creates a
recurring template pre-linked to the asset without leaving the page
- #6: Fix unoccupied rooms filter — was always empty because (a) list_type
was 'all' instead of 'staying' and (b) only checked b.site_id but NewBook
returns booking_site_id on most account configs
- #8: Add open-task count badges to category filter chips on Summary page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fastify rejects POST requests that carry Content-Type: application/json
but an empty body (JSON.parse('') throws). Both no-body POSTs now send {}.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unsellable flag is in-app visibility only; staff mark rooms out of
order in NewBook through their own process. NewBook use is now
read-only (room sync + occupancy filter).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>