Skip to content

chore(release): v1.2.0 - #362

Merged
jirhiker merged 180 commits into
productionfrom
staging
Aug 26, 2026
Merged

chore(release): v1.2.0#362
jirhiker merged 180 commits into
productionfrom
staging

Conversation

@jirhiker

@jirhiker jirhiker commented Aug 24, 2026

Copy link
Copy Markdown
Member

Release v1.2.0. 180 commits, 36 merged PRs, 199 files changed.

Merging this deploys to production.

Release prep already done

PR #361 landed on staging first and handled three things:

  • Version set to 1.2.0. staging had read 1.1.0 since July, because the 1.1.1 and 1.1.2 bumps went straight to production on hotfix branches and never came back.
  • Editor access to Unassociated Assets restored. The v1.1.1 hotfix (cac1710) changed both the nav entry and the access-control policy, but staging carried only part of it. Left alone, this merge would have shipped a nav link editors can see behind a permission check that turns them away, with git reporting no conflict on the file that mattered.
  • production back-merged into staging, so this merge is conflict-free rather than needing four conflicts resolved during the release.

What is in the release

Larger pieces:

  • MapLibre replaces Mapbox (BDMS-811), with the viridis palette for map symbols (BDMS-349) and contextual media mapping (BDMS-901)
  • Hydrograph correction workbench, manual measurement table, stored-data deletion, field-event provenance
  • Geothermal well grid, inventory and temp-depth pages (BDMS-878)
  • Projects as a top-level resource, wells and contacts on the shadcn DataTable (BDMS-1133)
  • GIS artifact downloads and a table view for the datasets page
  • Well ID minting
  • PostHog analytics disclosure (BDMS-994) and well detail pages opening in a new tab (BDMS-903)
  • On-demand preview deployments (BDMS-1173), Cypress reference-data seeding, linting toolchain update

Fixes: USGS Water Service API link (BDMS-1110), geothermal API URL, hydrograph corrector editor access, well show OSE POD and USGS cards.

Verification

  • staging into production merges with zero conflicts, verified locally against b416371
  • The merged tree carries version 1.2.0, editorRoles on the unassociated-assets policy, editorAndAbove on the nav entry, and staging's buildAuthentikUrl helper, which preserves the v1.1.1 trailing-slash fix
  • PR chore(release): prepare v1.2.0 #361 passed all six checks: production build, lint, Vitest, Cypress, preview deploy
  • 361 unit tests pass locally, tsc clean

Before merging, two open items

PR #346 (v1.1.2) is still open against production. Its payloads are already on staging, the PostHog disclosure in src/components/Auth.tsx and BDMS-903 via PR #325, so this release supersedes it. It should be closed rather than merged.

An earlier version of this description called tmp/wellpy-samples/ scratch data and suggested deleting it before release. That was wrong on both counts, and I am correcting it here so nobody acts on it.

Those four files are test fixtures. src/components/Hydrographs/hydrographCorrection.test.ts reads them from disk in seven places, and docs/hydrograph-correction-gap-analysis.md documents what each one broke in the parser. Deleting them turns those tests red.

They also never reach users. Vite copies public/ only, and vite.config.ts sets no publicDir override, so nothing under tmp/ is bundled or served. The cost is about 198 KB of repo weight.

The directory name is the problem, not the files. Moving them to src/test/fixtures/wellpy/ and updating the seven paths would stop the next reader drawing the same wrong conclusion I did. That is a separate cleanup, not a release blocker.

🤖 Generated with Claude Code

TylerAdamMartinez and others added 30 commits July 20, 2026 16:04
…e-new-or-remove-old-cypress-tests

test(cypress): add new tests for current UI & remove outdated tests
Scaffolding for the inline geothermal data-upload grid:
- geothermal-data-provider fetcher now sends a Bearer token from
  getAccessToken() and refreshes once on 401, mirroring the ocotillo
  provider. Prerequisite for writing edits back to the geothermal API.
- Register geothermalResources so refine is aware of them for routing
  and access control.
- Mount GeothermalRoutes at /geothermal/*; the UI was previously
  scaffolded but unreachable.

Nav still comes from config/navigation.ts, so nothing new appears in
the sidebar yet; a gated nav entry lands with the grid page. Also adds
the proposed-approach doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lift the Glide Data Grid patterns out of the throwaway DataGridPage
specimen into an entity-agnostic component under src/components/grid:

- EditableDataGrid<T>: generic over row shape + a GridColumnSpec column
  model (id, title, kind, editable, getValue/setValue, validate, onClick).
  Owns theme, auto-sizing, cell-kind dispatch (text/number/uri), and
  edit-to-row mapping; edits lift back via onRowsChange so the parent
  keeps the source of truth (needed for the Phase 3 batch save).
- gdgTheme: shared light/dark theme constants + useGdgTheme hook, off
  ColorModeContext. Removes the duplicated theme block from the demo.
- useElementSize: callback-ref ResizeObserver hook that attaches even
  when the grid mounts inside a portal (fixes the bulk-add modal case).

Refactor DataGridPage (main grid + bulk-add modal) to consume the new
component, proving the extraction against real data. The specimen stays
as reference until the geothermal grid page lands, then gets deleted.

validate on GridColumnSpec is defined but not yet surfaced — reserved
for inline cell validation in Phase 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an inline-editable spreadsheet of the records belonging to a well,
built on the reusable EditableDataGrid from Phase 1.

- GeoThermalRecordsGrid (pages/geothermal/wells/records-grid.tsx): reads
  wells/{id}/records through the geothermal provider, renders an editable
  grid, and accumulates cell edits in local state with a dirty-row count.
  No write-back yet — a disabled "Save changes" button marks where the
  Phase 3 batch save lands.
- Admin-gated via canManageGeothermal (useAccessCapabilities), per the
  BDMS-878 decision that both editing and entering rows require
  Geothermal.Admin. Query is disabled until the check passes.
- Route wells/records-grid/:id mounted in GeothermalRoutes; reachable
  from the well show page via a gated "Open data-entry grid" button.

Columns are PROVISIONAL — they mirror the current IWellRecord shape
(11 string fields) since the geothermal API contract is not finalized.
The column list is a single localized array to swap once the real
field set and types are pinned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire write-back for the geothermal records grid. No autosave — edits
accumulate and a "Save changes" action flushes every dirty row.

Provider:
- geothermal create/update now map FastAPI/Pydantic 422/409 payloads
  ({ detail: [{ loc, msg }] }) to Refine fieldErrors, mirroring the
  ocotillo provider. Shared throwOnWriteError + buildFieldErrors helpers;
  strips the leading body. segment from field paths.

EditableDataGrid:
- new cellErrors(rowIndex) => { colId: msg } prop; errored cells render
  with an error-tinted background so rejected fields surface inline.

Records grid:
- Save flushes dirty rows via provider.update through Promise.allSettled
  (no server bulk endpoint → one request per row). Per-row tracking:
  saved rows update their snapshot and clear dirty; failed rows stay
  dirty for retry and tint their rejected cells from fieldErrors. Toolbar
  shows an "n saved, m failed" summary and disables Save while in flight
  or when nothing is dirty.

New-row creation (create path) is Phase 4; this only saves edits to
existing rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add new-record entry to the geothermal records grid, flushing through
the Phase 3 batch save via the provider create path.

- "Add N rows" appends blank rows to the same grid. New rows carry a
  client-only temp id (new:N) in OBJECTID until the server assigns a
  real one, so their ID cell stays blank and create is distinguished
  from update. No separate bulk-add modal / upload step — entry happens
  inline in the same spreadsheet, so Glide's copy/paste-from-Excel works
  for new rows too.
- Save now builds a pending-op list: changed existing rows -> update,
  non-blank new rows -> create (blank appended rows are ignored). Runs
  all through Promise.allSettled with per-row tracking. A created row
  adopts the server-returned record (real OBJECTID) and stops being new;
  failed rows stay pending for retry with their cells tinted from
  fieldErrors. Unsaved count and summary now include creates.

Assumes create returns the created record; reconciliation of the temp
row relies on that response. The demo BulkAddModal stays as its own
reference and is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the pure save logic out of the records-grid page into
recordsGridLogic.ts (rowKey, isNewRow, isBlankNew, makeBlankRecord,
flattenFieldErrors, computePendingOps) so it is unit-testable without
mounting the Glide canvas. The page imports from it; behavior unchanged.

Tests:
- recordsGridLogic.test.ts (9): computePendingOps splits changed
  existing rows -> update and non-blank new rows -> create, skips blank
  appended rows and unchanged rows, preserves row indices for mixed
  batches; makeBlankRecord / isNewRow / isBlankNew / flattenFieldErrors.
- geothermal-data-provider.test.ts (5): create/update map 422 and 409
  Pydantic payloads to fieldErrors (stripping the body. prefix, mirrored
  onto .errors), throw the raw Response for non-validation statuses and
  for a 422 without a detail array, and return { data } on success.
  fetch + getAccessToken + settings are mocked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Flip SHOW_EXAMPLE_NAV to true. The sandbox "Example" section already
carries a "Data Grid" item linking to /example/data-grid (the Glide
Data Grid specimen); it was just gated off. No new plumbing — the item,
route (App.tsx) and page (pages/example/DataGridPage.tsx) all exist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the label users refer to. Only the visible label + tooltip change;
the /example/* routes and specimen pages are untouched. Also add
.claude/launch.json (npm run dev on :5173) for local preview.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Sandbox "Data Grid" item is the ocotillo-wells design specimen, not
the geothermal grid. Add a distinct "Geothermal Records" entry that
opens the real geothermal data-entry grid.

- GeoThermalRecordsGridPicker: the geothermal grid is scoped to one well
  (records-grid/:id), so the Sandbox entry lands on a picker that lists
  geothermal wells and navigates into the chosen well's grid. Admin-gated
  (canManageGeothermal), same as the grid.
- Route wells/records-grid (index) -> picker, alongside the existing
  wells/records-grid/:id -> grid.
- Sandbox nav gains a "Geothermal Records" sub-item; the section's
  open/active detection now also matches the geothermal grid path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add canEnterGeothermalData(canManageGeothermal) = canManageGeothermal ||
import.meta.env.DEV. Under `vite` dev (and vitest) DEV is true, so the
records grid, its well picker, and the well-show entry button skip the
"You need the Geothermal Admin role" block for local testing. Production
builds (`vite build`) set DEV=false, so the gate stays fully enforced
when deployed. Single greppable helper (BYPASS_GEOTHERMAL_GATE) to remove
if the bypass is ever unwanted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The local dev fallback for VITE_NMBGMR_GEOTHERMAL_API_URL was :8008, but
the geothermal API runs on :8000. Prod is unaffected — it sets the env
var explicitly; this only changes the empty-env local default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The geothermal wells live under thing/geothermal-well (mirroring
thing/water-well), not a top-level /wells. Update every geothermal
resource string:
- wells list (list page, records-grid picker) -> thing/geothermal-well
- well detail + bore (show page) -> thing/geothermal-well
- records (grid read + save, show page grid) ->
  thing/geothermal-well/{id}/records

Route paths (/geothermal/wells/...) are unchanged — only the API
resources moved. Records nesting is inferred from the prior
wells/{id}/records pattern; adjust if the real records path differs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The thing/geothermal-well endpoint returns { items, total, page, size,
pages }, but the provider's getList returned the whole envelope as
`data`, so consumers got an object where they expected an array —
GeoThermalRecordsGridPicker crashed on `wells.map`.

- getList now unwraps { items, total } when present, still accepts a
  bare array (older endpoints), and falls back to an empty list for any
  other shape (never returns a non-array).
- Picker also filters out rows without a usable OBJECTID, since a Radix
  SelectItem with an empty value throws.
- Tests: getList unwraps the envelope, handles a bare array, and returns
  [] for an empty envelope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /thing/geothermal-well returns snake_case fields (well_data_id,
name, county, well_type, status, operator, ...), not the guessed
OBJECTID/WellDataID. The picker filtered on OBJECTID (null), so all 8
wells were dropped ("No wells found").

- IWell: real geothermal well shape; well_data_id (UUID) is the id used
  for detail/records routes.
- Picker: id/value = well_data_id, label = "name — county, well_type",
  filter on well_data_id.
- List page: columns name/well_type/status/county/operator, row id and
  edit/show targets = well_data_id.
- Show page: detail fields -> name / well_data_id / county.

Records grid + its columns (IWellRecord) are unchanged — the records
model stays; the /records endpoint is not implemented yet (404).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spec for inventorying new geothermal wells via CSV upload or direct
Glide-grid entry, feeding one editable grid + batch create. Grounded in
the real thing/geothermal-well contract and reusing EditableDataGrid,
the provider create + fieldErrors mapping, and the batch-save pattern.

Decisions folded in: papaparse for CSV, create-all (server 409 on
conflict), dropdowns for enum fields, Sandbox nav. Open: required-field
set (not in OpenAPI) and dropdown value source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First phase of the well-inventory feature (spec:
docs/geothermal-well-inventory.md). An editable Glide grid for entering
new geothermal wells directly.

- GeoThermalWellInventory: EditableDataGrid over blank well drafts
  (WellDraft = well fields minus the server-assigned well_data_id).
  Columns for every user-entered field (text + number kinds); "Add rows"
  appends blank rows; a live "N wells to add" count tracks non-blank
  drafts. Admin-gated via canEnterGeothermalData (dev-bypassed).
- Route wells/inventory; Sandbox nav gains "Geothermal Inventory" and
  its open/active detection covers the inventory path.

Entries are local-only for now. Batch create (POST) is P2, CSV load P3,
and dropdown/date/boolean editors + validation P4 — the "Create wells"
button is present but disabled until P2. Verified: grid renders, cell
edits commit, and the count updates live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- accessControl.test.ts: geothermal resources (geothermal.dashboard,
  geothermal.geothermal_wells) are now registered, so the decision-
  complete matrix must account for them. They belong to the geothermal
  portal — granted to Geothermal roles (list/show), not AMP. Updated the
  expected routable set and per-scenario access accordingly. Fixes the
  Vitest Test Suite failure.
- navigation.ts: gate SHOW_EXAMPLE_NAV on import.meta.env.DEV so the WIP
  Sandbox section doesn't ship to production (Copilot review).
- records-grid.tsx: the ID column held a string OBJECTID (and "new:N"
  temp ids) in a number cell; switch to a read-only text cell, blank for
  new rows (Copilot review).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the inventory "Create wells" action. Every non-blank draft POSTs to
thing/geothermal-well via the geothermal provider (one request per row —
no server bulk endpoint), through Promise.allSettled with per-row
tracking:
- created wells drop out of the grid; a "N created" summary reports them.
- failed rows stay for retry with their rejected cells tinted from the
  provider's Pydantic fieldErrors (422/409), and remapped to the
  surviving rows' new indices.
- cleanDraft strips empty fields so the payload carries only entered data.
- Create button enables only with non-blank rows and shows "Creating…"
  while in flight.

Verified live: a filled row POSTs, the failure is tracked per row
(the backend create endpoint isn't implemented yet — GET-only), the row
is retained, and the summary shows "0 created, 1 failed". Success path
(drop + count) mirrors the tested records-grid save. CSV load is P3;
dropdown/date/boolean cells + client validation P4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add CSV upload + a matching downloadable template to the inventory grid.

- inventoryFields.ts: extract the well-draft model (field lists, headers,
  isBlankDraft, cleanDraft) so the grid, CSV, and tests share one source.
- inventoryCsv.ts (papaparse): parseCsvFile loads a File into well drafts;
  mapRecordsToDrafts (pure) matches headers to fields case-insensitively
  and trimmed, coerces number fields (dropping unparseable), collects
  unknown headers, and skips empty rows. buildTemplateCsv emits a header
  row of the canonical field names.
- inventory.tsx: "Upload CSV" (hidden file input) loads parsed rows into
  the grid with a status line ("Loaded N rows · ignored columns: … · M
  malformed rows skipped"); "Download template" saves the header CSV.
- Tests for the mapper (coercion, case-insensitive match, unknown-header
  collection, empty-row skip) and the template header row.

Verified live: uploading a CSV populates the grid, maps recognized
columns, and reports an ignored unknown column. Dropdown/date/boolean
cells + client validation land in P4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finish the inventory grid with typed editors and client-side validation.

EditableDataGrid:
- add 'boolean' (native checkbox) and 'dropdown' cell kinds; dropdown
  uses @glideapps/glide-data-grid-cells (allCells custom renderers) with
  a per-column `options` list. CellValue widened to include boolean.

Inventory:
- enum fields (well_type, well_class, status) render as dropdowns;
  has_geothermal_data as a checkbox. Provisional option lists in
  inventoryFields (ENUM_OPTIONS) — observed from live data, confirm with
  backend.
- required-field validation (REQUIRED_FIELDS / missingRequired, also
  provisional): non-blank rows missing a required field tint those cells
  and block "Create wells", with a "N rows missing required fields" note.
  Validation errors merge with server fieldErrors for display.

Verified live: a CSV load tints the incomplete row's required cells,
shows the missing-rows note, and disables Create; the complete row is
clean. No console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per decision, county/state are reverse-geocoded from latitude/longitude
by the backend, auto-filling only when left blank. So the frontend does
no geocoding and no longer requires county/state — latitude/longitude
are the required location inputs; county/state stay optional (a user or
CSV may still provide them, which the server keeps).

- REQUIRED_FIELDS drops county/state (keeps name, api, well_type,
  latitude, longitude).
- Spec §6/§6a document the server-side derivation contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Save for later action that persists the non-blank inventory rows
to localStorage, so entered/imported data survives navigation and
reloads.

- loadDraft/saveDraft/hasDraft helpers (guarded for unavailable storage).
- On mount, restore any saved draft (+ a few blank rows) and show a
  "Restored N saved rows" note; otherwise start with blank rows.
- "Save for later" button writes the current non-blank rows and shows a
  "Saved N rows for later" note; saving an empty grid clears the cache.
- After a successful create, keep an existing saved draft in sync with
  the surviving rows so created wells don't reappear on reload.

Verified live: load rows → Save for later → reload restores them with
the status note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Set rowMarkers="number" on the inventory EditableDataGrid so each row
shows its number in the left gutter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- LocationPickerModal: a Dialog with a react-map-gl satellite map; click
  to drop/move a pin, confirm to return lat/lon. Rendered per row (keyed)
  so it opens with that row's current coordinates.
- Inventory: a read-only "Location" column whose cell opens the picker
  for its row ("📍 Pin on map", or the current coords); confirming writes
  latitude/longitude back to that row.
- EditableDataGrid: onClick now receives the row index (needed to target
  the right row); add an optional per-column `format` that overrides only
  the displayed string, not the stored edit value.
- Coordinates display rounded to 7 decimal places (formatCoord) in the
  lat/lon cells, the Location column, and the picker readout — full
  precision is kept for save.
- Header: "Total depth" → "Total Depth (ft)".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-toolchain

feat(biome.json): replace ESLint and Prettier with Biome
jirhiker and others added 20 commits August 21, 2026 14:10
Adds a debounced search box to the top-left panel stack on the map view.
Selecting a result fits the map to the result's bounding box (or eases to
its center) and drops a marker; results are biased toward the current
viewport center.

Geocoding goes to Photon, komoot's OpenStreetMap-backed service. Photon
needs no account or token, which keeps the map free of API keys the way
basemaps.ts already does, and unlike Nominatim its usage policy permits
search-as-you-type. Photon returns address components rather than a
formatted label and reports `extent` as [minLon, maxLat, maxLon, minLat],
so utils/geocode.ts composes the display label and reorders the box into
the [west, south, east, north] order fitBounds expects. Results are
filtered to the US client side, since Photon has no country parameter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two cost fixes now that the ephemeral path is proven working.

The API container was provisioned 2 vCPU and 2Gi. The expensive part of startup
-- migrate plus seed -- measured in seconds on the first successful run, so the
headroom was buying nothing. Drop it to 1 vCPU and 1536Mi, taking the service
from 3 vCPU / 4Gi to 2 vCPU / 3.5Gi across both containers.

The sizing matters more than it looks: the database is the instance, so the
service cannot scale to zero and bills continuously from the moment it is
deployed until teardown.

Second, deploying a preview with backend=staging now deletes any ephemeral API
left over from an earlier ephemeral deploy of the same branch. Dropping the
preview-backend label and pushing used to redeploy the frontend against staging
and silently strand the old always-warm API, with nothing reclaiming it until
the PR closed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A query like "socorro" matches both the city and the county relation, and
both compose to "Socorro, New Mexico" — two identical rows in the dropdown
that fly to different extents. Where a label repeats, Photon's own type
classification is now appended to each of the colliding rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…and-preview-deployments

ci(preview): on-demand previews from any branch, teardown, ephemeral backend
…hed layers

Layer resolution scored partial matches: exact 100, prefix 60, suffix 50,
substring 20, with no minimum score and `exists: Boolean(bestMatch)`. Any
substring hit bound a layer. With `locations` removed from the catalog per
BDMS-978, the Locations layer would have silently rebound to
`rock_sample_locations` (normalized, it ends with "locations"), rendering
that collection twice on the map and displacing the real rock-sample entry
on the collections page through its `seenCollectionIds` dedupe.

Resolution is now exact against `id`/`collection_id`, then `name`/`title`,
candidates tried in order. Separators and case are still normalized so one
candidate covers both `water_elevation_contours` and its display title.
A layer either names a published collection or does not exist. The regex
token scorer that backstopped the water-elevation layers is gone too.

Verified every registered layer against the deployed catalog: all 18 that
survive the BDMS-977/978/979 trim resolve exactly, in both the map hook and
the collections page.

Removes the layers whose collections those tickets unpublish:

  ogc-locations               locations                    BDMS-978
  ogc-latest-depth-to-water   latest_depth_to_water_wells  BDMS-977
  ogc-average-tds             avg_tds_wells                BDMS-977
  ogc-other-thing-types       other_things                 BDMS-979

All three bound by title, not id, which is why the shorter candidate lists
still matched them today.

Also removes ogc-water-elevation-contours: the catalog publishes no contour
collection and never did, so the entry was dead before this change. Its
derived counterpart, computed from water_elevation_wells via turf, was gated
on `!waterElevationContours.exists` -- always true -- so that gate is now
dropped and the derived layer is unconditional. Same runtime behavior.

DEFAULT_VISIBLE_LAYERS pointed at ogc-latest-depth-to-water, so the map would
have opened with nothing. It now opens on ogc-water-well-summary, the layer
BDMS-977 names as making latest-depth-to-water redundant.

DEPTH_LEGEND, latestDepthToWaterColorFromFeature, and
averageTdsColorFromFeature had no remaining consumers and are removed.

Merge order matters: this must not reach staging before the pygeoapi catalog
change deploys, or four working layers disappear with nothing behind it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AGENTS.md already documents that feature, fix, chore, docs, and CI branches
all base off staging, but there was no CLAUDE.md on any branch, so Claude
Code never loaded it. Cutting a branch from the default branch, production,
produces a PR into staging that drags every commit staging is missing.

Imports AGENTS.md and restates the branching rule up front. Carries the
model-attribution frontmatter the repo's documentation rule requires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Sandbox nav group mixed a typography specimen with three WIP geothermal
grids, and its visibility flag treated staging as a place to try things out.
Previews are the sandbox now: a PR preview can run against an ephemeral API
nobody else shares, while staging is a pre-production release branch.

- Remove the Sandbox nav group and its SHOW_EXAMPLE_NAV flag. The pages it
  linked (/example/typography and the three geothermal grids) stay routable by
  URL; a geothermal nav group lands separately.
- Remove the Sandbox access-control resource, the 'Sandbox' parent on
  ocotillo.hydrograph-correction, and the Sandbox label case in the Refine
  sider.
- Document the staging-vs-preview rule in AGENTS.md and update the docs that
  described nav placement as Sandbox.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…x-concept

chore(nav): drop the Sandbox concept
…p-colors

feat(map): color map symbols with the viridis palette
…-catalog

Conflicts came from the viridis colour work (#350) editing the very layers this
branch removes, so every conflict was the same shape: this branch deletes a
block, staging edits it.

Resolved in favour of the deletions, which is the point of the branch — the
collections behind them are not published:

- ogcLayerUtils: DEPTH_LEGEND, averageTdsColorFromFeature,
  latestDepthToWaterColorFromFeature
- useThingLayers: locationsLayer, latestDepthToWaterLayer, averageTdsLayer,
  waterElevationContoursLayer, waterElevationContoursLayerStyled,
  otherThingTypesLayer

Staging's changes to the layers this branch keeps merged cleanly and are
untouched. Nothing references the removed symbols afterwards; typecheck is
clean and the unit suite passes (252 tests).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DEPTH_CLASS_COLORS only fed latestDepthToWaterColorFromFeature, which went with
the unpublished layers. Staging added the constant alongside the viridis work,
so the merge kept it with nothing left to use it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The catalog publishes geothermal_wells_bht,
geothermal_wells_temperature_profile, bht_measurements,
temp_depth_measurements, heat_flow, and dst, but no layer named any of
them, so none reached the map or the collections page. The collections
page even carried a Geothermal group described as reserved for these
"when they are published" -- they are published.

Registers all six in both registries, with the collection id as the first
candidate and the display title as the fallback. Exact matching means the
id is what binds; the titles carry em dashes, which normalize away, so
either form resolves.

The map's layer panel had no geothermal group -- getLayerGroupKey would
have dropped all six into Reference. Adds the group between Climate and
Geoscience, matched on 'geothermal', 'bht', 'temp-depth', 'heat-flow',
and an exact 'ogc-dst'. The dst key is compared whole rather than by
substring, since three letters collide too easily. The check runs first,
before the groundwater branch, so a name like
ogc-geothermal-wells-temperature-profile cannot be claimed by another
group later.

Popup labels are added for all six. No layer-specific popup rows yet --
the property shapes are unverified, so the generic renderer handles them
until someone confirms what each collection carries.

Layers are off by default, as every layer other than the summary is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The API now serves ready-made QGIS and ArcGIS Pro artifacts for our OGC API
layers, so a user can open our data in a desktop GIS without configuring a
connection by hand. Nothing in the UI surfaced them.

Adds a panel above the dataset groups with the QGIS connections file, the QGIS
import instructions, and a copyable service URL for ArcGIS Pro, which has no
importable connection file. Per-layer .qlr / .lyrx downloads render on the
collection row they belong to, matched on the catalogue's `collection` field
against the collection id the page already resolves.

Layer ids are API-side config and change, so nothing here hardcodes them, and
hrefs and filenames are used verbatim from the catalogue: CORS on the API
exposes no headers, so `Content-Disposition` is unreadable from JS. Anonymous
artifacts are plain anchors; the authenticated internal-connections file is the
one blob round-trip, gated on AMP viewer access.

Types are hand-written zod for now. The committed openapi-auth.json snapshot has
no /gis paths, and the only source for them is an unmerged API branch whose spec
also carries unrelated unreleased endpoints and eight changed schemas, including
WellResponse and ThingResponse. Refreshing the whole snapshot to reach /gis
would regenerate all of that here. src/utils/gisArtifacts.ts is scoped to the
GIS surface and marked for replacement once /gis is in the deployed spec.

Contract and the follow-up notes: docs/gis-artifact-downloads-contract.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The card grid buries the catalogue: four group cards, each a column of
paragraphs, so comparing datasets means scrolling past descriptions. A table
puts all 24 on one screen — which is what this page is for — so it is now the
default, with a Cards/Table toggle in the header to get the old layout back.

Group membership is carried by row tint plus a labelled band per group rather
than a repeated column, and the collection id is dropped from the dataset
column: it is machine detail, and the title is what a reader scans by.

The page also runs wider than the old `lg` container, which left the table
cramped — full width up to a widescreen monitor, then ten of twelve columns so
rows do not run the whole span of a very wide display. The "Admin View"
overline is gone; the nav already gates who gets here.

buildCollectionRows and the shared collection field resolution live in
src/utils/collectionsView.ts so both views agree on what a dataset is called.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…uate-and-determine-public-pages

docs(public-page): add public page content audit
…-view-632abf

feat(map): BDMS-905. add place/address geocoder search to the map view
…t-downloads

feat(collections): desktop GIS downloads and a table view for the datasets page
Bump the version to 1.2.0. staging has read 1.1.0 since July, because the
1.1.1 and 1.1.2 hotfix bumps went straight to production and were never
merged back.

Also restore editor access to Unassociated Assets. The v1.1.1 hotfix
(cac1710) changed both the nav entry and the access-control policy, but
staging carries only part of that: the policy here had reverted to
adminRoles while production grants editorRoles. Merging staging into
production would have kept production's nav line and taken staging's
policy, leaving a nav link editors can see behind a permission check that
turns them away. Git reports no conflict on that file, so nothing would
have flagged it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Back-merge production so staging stops drifting from it. The 1.1.1 and
1.1.2 hotfixes shipped straight to production and were never merged back,
which is why staging still read 1.1.0 and why the Unassociated Assets
policy had reverted to admin-only here.

Conflict resolutions:

- package.json: keep 1.2.0.
- .gitignore: keep staging's Claude Code entries; production has none.
- authentik-provider: keep staging's buildAuthentikUrl helper. It strips
  trailing slashes from the base URL (src/config/auth.ts:7), so the v1.1.1
  fix for a trailing slash in AUTHENTIK_URL survives.
- accessControl.test: keep both sides. hydrograph-correction is new on
  staging, asset-unassociated comes from the v1.1.1 hotfix, and editors
  should have both.

With this in place the staging into production merge is conflict-free.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jirhiker
jirhiker merged commit 3b27bec into production Aug 26, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants