Mobile operators were never seeing the inline PDF preview, .TXT
download, or Review form that v0.13.0 added — every feature was
working on desktop browsers but invisible in the PWA.
Root cause: backend/static/sw.js had CACHE_VERSION = 'v1', unchanged
since v0.12.x. The activate handler deletes any cache not matching
CACHE_VERSION, so without a bump the stale sfm-static-v1 cache (with
the pre-v0.13.0 event-modal.js) stayed authoritative. cacheFirst
strategy served it forever; mobile users effectively saw the v0.12.x
modal regardless of how many times we rebuilt the image.
Fix:
- CACHE_VERSION bumped to 'v0.13.2' (matches backend/main.py VERSION).
Comment in sw.js documents the convention: any release touching a
static asset must bump this string.
- event-modal.js added to the precache list so its lifecycle is
explicitly tied to the SW version bump (installed fresh on activate
rather than landing via the cacheFirst-then-cached pattern).
Mobile users get the new modal on next page nav: SW update check
picks up the bumped sw.js, skipWaiting installs it, activate evicts
the v1 caches, controllerchange fires, page reloads, fresh
event-modal.js loads. Worst case ~1h delay from
registration.update() interval; operators can force-refresh by
closing + reopening the PWA.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviewed-on: #54
docs+chore: v0.12.1 — Unit Swap wizard, editable timeline, roster/tz fixes
CHANGELOG entry for the five commits that landed after the v0.12.0 tag:
two features (Unit Swap wizard at /tools/unit-swap, editable deployment
timeline on /unit/{id}) and two correctness fixes (RosterUnit.deployed
now flips on swap/unassign/promote; deployment timeline now respects
user timezone for both display and edits). No schema migrations.
README bumped to v0.12.1 with new bullets for the post-v0.12.0 features
and several already-shipped items that were missing from the list (SFM
Event DB Manager, Deployment-History calendar + Gantt tabs, reusable
location-map partial). backend/main.py VERSION constant bumped too.
## [0.11.0] - 2026-05-15
Operator-facing polish release. All work builds on the v0.10.0 SFM integration foundation — this release is about making the day-to-day workflows (managing locations, cleaning up bad attributions, browsing deployments) faster and less error-prone.
### Added
- **Soft-remove monitoring locations** (`POST /api/projects/{p}/locations/{l}/remove` + `/restore`): mark a location as no longer actively monitored without destroying historical events. Cascade-closes active unit assignments and cancels pending scheduled actions at the location. Restored locations rejoin the active list (assignments are NOT auto-reopened — operator creates new ones if resuming). Project page splits locations into Active and Removed sections; removed cards are greyed out, badged with the removal date + reason, and offer a Restore button.
- **Per-unit deployment Gantt chart** above the existing Deployment Timeline list on every seismograph unit detail page. Plain-SVG rendering, color per location, today marker (orange dashed line), reduced-opacity bars for closed assignments, blue outlines on metadata-backfilled assignments, dashed blue underlines marking mergeable groups. Click a bar to scroll the matching list row into view with a flash highlight.
- **Merge consecutive same-location assignments** (`POST /api/projects/{p}/assignments/merge`): operators often end up with several rows representing one continuous deployment (after remove/restore, or metadata-backfill adjacent to a manual record). Now auto-detected and surfaceable in the timeline header — one click combines them into a single record. Preserves the earliest record's notes + ingest source, writes an `assignment_merged` audit entry, deletes the others.
- **Delete assignment for mis-clicks** (`DELETE /api/projects/{p}/assignments/{a}`): hard-deletes a bogus assignment row that was never a real deployment. Trash icon in each row of the location's Deployment History panel. Refuses the delete if any `MonitoringSession` exists in the assignment's window — those should go through Unassign instead, which preserves audit history. Writes an `assignment_deleted` UnitHistory row.
- **Drag-to-reorder location cards**: each active card has a six-dot drag handle on the left. Drag/drop reorders the DOM and persists via `POST /api/projects/{p}/locations/reorder`. Implementation uses native HTML5 drag-and-drop (no library). New locations land at the end (`sort_order = max + 1`); removed locations stay sorted by removal date.
- **Three-dot kebab menu on location cards**: replaces the four inline pill buttons (Unassign / Edit / Remove / Delete) with a single ⋮ menu. Click ⋮ to open; click outside or Escape to close; only one menu open at a time.
- **Event count on vibration location cards**: vibration cards now show "{N} events" sourced from SFM via concurrent fan-out, instead of "Sessions: 0" (sessions don't exist under the watcher-forward pipeline). Sound locations still show session counts.
- **Project overview location map**: right column of every project's overview replaces the lightly-used Upcoming Actions panel with a Leaflet map. One pin per active monitoring location (parsed from the `coordinates` field). Click pin → scrolls + flashes the matching card. Tooltip on hover. Locations without coordinates surface as an inline hint below the map. If the project has pending scheduled actions, a small "{N} upcoming actions →" link appears in the card header that switches to the Schedules tab.
### Changed
- **Backfill location fuzzy matcher is now stricter**: `rapidfuzz.WRatio` was over-confident on location names because their shared boilerplate vocabulary ("Area", "Loc", numbers) inflated scores. Example false positive that prompted the change: `"Area 2 - Brookville Dam - Loc 2 East"` vs `"Area 1 - Loc 1 - 87 Jenks"` scored 86% via WRatio. Now uses `token_set_ratio` as the base scorer plus a 0.30 penalty when the two strings have disjoint multi-digit numeric tokens. Catches the "same project, different address number" case (`"68 Jenks"` vs `"87 Jenks"`) that pure token-set scoring still rated above 0.90. Project matching keeps WRatio (where its leniency is desirable for typos like `1-80` vs `I-80`).
### Fixed
- **Three separate JSON.stringify quote-collision bugs**: any inline `onclick="...({...} | tojson)"` or `onclick="...${JSON.stringify(x)}..."` where `x` contained any character that JSON quotes (essentially every real-world string) broke the HTML attribute and silently un-bound the click handler. Surfaced in three places this release; all fixed by switching to `data-*` attributes plus a trampoline function reading from `this.dataset`:
- **Location Remove button** on the project page
- **Metadata-backfill typeahead dropdown** (existing project + location pickers)
- **Project-merge typeahead dropdown** (in the per-project header)
- **Project-merge modal too short to show typeahead options without scrolling**: modal body's `flex-1 overflow-y-auto` collapsed tight; added `min-height: 480px` to the modal container + `min-h-[320px]` to the body so the dropdown always has room.
- **Project location map covered modals**: Leaflet's internal panes carry z-indexes 200–800 by default and the map container didn't establish a stacking context, so those z-indexes leaked into the root and outranked modals' `z-50`. Fixed by adding `isolation: isolate` to the map container.
- **`delete_assignment` crashed with `AttributeError`**: the safety check queried `MonitoringSession.start_time` but the actual column is `started_at`. Every DELETE call to `/assignments/{id}` failed with 500 before doing anything.
### Migration Notes
Run on each database before deploying. Both migrations are idempotent and non-destructive.
```bash
docker exec terra-view-terra-view-1 python3 /app/backend/migrate_add_location_removed.py
docker exec terra-view-terra-view-1 python3 /app/backend/migrate_add_location_sort_order.py
```
Or sweep all migrations at once (safe — already-applied ones no-op):
```bash
for f in backend/migrate_*.py; do
docker exec terra-view-terra-view-1 python3 "/app/backend/$(basename $f)"
done
```
New columns added this release:
- `monitoring_locations.removed_at` (DATETIME, nullable) — NULL means active
- `monitoring_locations.removal_reason` (TEXT, nullable)
- `monitoring_locations.sort_order` (INTEGER, default 0) — seeded to alphabetical-index per project on first migration
**Deploy order matters**: migrations must run BEFORE the new code is up, otherwise the running app will throw 500s on the unrecognized columns. Idempotent migrations make this recoverable but it's better avoided — the v0.11.0 deploy on prod hit this exact window after the v0.10.0 release.
---
@@ -5,6 +5,21 @@ All notable changes to Terra-View will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.13.2] - 2026-05-30
PWA-cache fix for mobile operators. v0.13.0 added the inline PDF preview, `.TXT` download, and Review form to `event-modal.js`, but mobile devices using Terra-View as a PWA never saw any of it — the service worker had `CACHE_VERSION = 'v1'` (unchanged since v0.12.x), so the activate handler never evicted the stale cache and mobile users kept getting served the pre-v0.13.0 modal forever.
### Fixed
- **Service worker cache version bumped + tied to the app version**. `CACHE_VERSION` in `backend/static/sw.js` is now `'v0.13.2'`, which causes the SW's activate handler to delete the old `sfm-static-v1` / `sfm-dynamic-v1` / `sfm-data-v1` caches on first visit after the upgrade. Going forward the convention is: any release that touches a static asset must bump `CACHE_VERSION` to match `backend/main.py`'s `VERSION`. Comment in `sw.js` documents this.
- **`event-modal.js` precached** alongside `mobile.js` / `offline-db.js` etc. Lifecycle is now tied to the SW version bump explicitly — old modal JS gets evicted on activate, new modal JS is fetched and cached during install.
### What mobile users will see after deploy
On next page navigation the SW update check fires, the new SW installs (skipWaiting), activate evicts the v1 caches, `controllerchange` fires, the page reloads with the v0.13.x modal. On the worst-case device (no recent visit), it might take up to an hour for `registration.update()` to pick up the new SW — operators can force-refresh by closing and re-opening the PWA, or by clearing site data once.
---
## [0.13.1] - 2026-05-29
## [0.13.1] - 2026-05-29
Same-day patch on top of v0.13.0. Fixes the mic-chart unit default — v0.13.0 shipped with `dBL` as the default, but the PDF report renders the mic axis in psi, so the website chart and the printed report didn't match. Operator caught it within an hour of rollout. Also relabels the modal's "Captured at" field to "Time received" so it isn't mistaken for the device's trigger time.
Same-day patch on top of v0.13.0. Fixes the mic-chart unit default — v0.13.0 shipped with `dBL` as the default, but the PDF report renders the mic axis in psi, so the website chart and the printed report didn't match. Operator caught it within an hour of rollout. Also relabels the modal's "Captured at" field to "Time received" so it isn't mistaken for the device's trigger time.
Backend API and HTMX-powered web interface for managing a mixed fleet of seismographs and field modems. Track deployments, monitor health in real time, merge roster intent with incoming telemetry, and control your fleet through a unified database and dashboard.
Backend API and HTMX-powered web interface for managing a mixed fleet of seismographs and field modems. Track deployments, monitor health in real time, merge roster intent with incoming telemetry, and control your fleet through a unified database and dashboard.
// Files to precache (critical app shell). event-modal.js is included
// so its cache lifecycle is tied to the SW version bump explicitly.
constSTATIC_FILES=[
constSTATIC_FILES=[
'/',
'/',
'/static/style.css',
'/static/style.css',
'/static/mobile.css',
'/static/mobile.css',
'/static/mobile.js',
'/static/mobile.js',
'/static/offline-db.js',
'/static/offline-db.js',
'/static/event-modal.js',
'/static/manifest.json',
'/static/manifest.json',
'https://cdn.tailwindcss.com',
'https://cdn.tailwindcss.com',
'https://unpkg.com/htmx.org@1.9.10',
'https://unpkg.com/htmx.org@1.9.10',
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.