Versions
Release history — what changed in each version of Open Gauge, and why.
Open Gauge tracks one version number across the frontend and backend. It's displayed at the
bottom-left of the sidebar (v3.6.1 · self-hosted) and in the OpenAPI schema. This page mirrors
VERSIONS.md at the repo root.
History below 1.0.0 is retrofitted from the git commit log, numbered by semantic-versioning
impact (0.x while the product was pre-release/unstable, 1.0.0 at the point it became a
documented, licensed, self-hostable product). Patch releases (x.y.Z) break out the smaller
fixes and incremental additions that landed between each minor version.
3.6.1
Changed
- Scrollbars now match the app's own colors instead of the browser default, documented in
UI.md's new Scrollbars section. - Notification bell button now hovers the same way as the language/theme buttons.
- User profile picture in the top bar enlarges on hover.
Fixed
- A user's profile picture on their
/users/{id}detail page didn't open the preview modal on click — it now uses the sharedImageUploadField(read-only mode), matching every other picture in the app. - Tooltips inside a collapsed panel's field rows (asset overview) were clipped.
CollapsibleSectionno longer clips overflow to round its corners — the header button gets conditional rounding instead. - Tooltip "view documentation" links 404'd in demo mode —
tooltip.tsxnow uses the locale-awareLinkfrom@/i18n/navigationinstead of plainnext/link.
3.6.0
Changed
- Assets are now navigated by their human-readable asset ID (e.g.
OG-00042), not the internal UUID. Every place that links to an asset detail page — the asset registry list and grid, dashboard widgets (Upcoming calibrations, Recent assets, Activity), top-bar search, and the "New/Duplicate/Import asset" flows — now builds the URL fromasset_idinstead ofid. QR/sticker labels now encode the sameasset_id-based URL. Every/assets/{ref}endpoint (and its nested resources) resolves{ref}as either theasset_idor the internal UUID, so old bookmarked or previously-printed UUID-based links keep working unchanged.
Fixed
- Demo mode 404'd when clicking an asset from the registry — the registry's list/grid
links were plain
<a href>tags instead of next-intl's locale-awareLink, so the un-prefixed URL 404'd in the demo's static export (no middleware to add the locale prefix). Switched toLinkfrom@/i18n/navigation, matching every other asset link in the app.
3.5.2
Fixed
- Activity register panel's description sat flush against the user column instead of
reading toward the right edge of the panel, and never showed a user's profile picture
even when one was set.
dash_repo.get_activity(apps/api/app/repositories/dashboard.py) never resolvedprofile_picture_idinto a presigned URL the way the full/activitypage'saudit_log_enrich.enrich()already does — soActivityFeedalways fell back to initials.ActivityItem(schema + frontend type) now carriesactor_profile_picture_url, resolved via the shareduser_profile_svc.resolve_picture_url, andActivityFeed's description column is right-aligned to match the "Upcoming calibrations" panel's right-aligned date column.
3.5.1
Fixed
- Upcoming calibrations panel showed overdue calibrations, and some due dates were wrong.
dash_repo.get_calibration_eventspicked each asset's most recently created calibration record to determine its due date; on an asset with multiple independently-scheduled channels (e.g. two sensor channels calibrated at different times), the newest record isn't necessarily the one that governs the asset's current status, so an asset could be flagged overdue even though its actual next due date (the furthest-out among its active calibrations) was still in the future. The query now aggregatesmax(due_date)per asset — the same logic already used byrepositories/asset.py::list_assetsand the dashboard summary's status counts — and only returns assets whose due date is today or later, so the panel no longer double-duties as an overdue list. - Activity register rows dumped all content in one left-aligned column. The dashboard's
ActivityFeedrows now lay out as user (with timestamp underneath) in a fixed-width left column and the action/diff description in the remaining space, instead of a single stacked block; removed the decorative blue dot bullet.
3.5.0
Added
- Google Analytics on project-operated deployments only. A new
<GoogleAnalytics />component (apps/web/src/components/google-analytics.tsx) conditionally loads gtag.js whenNEXT_PUBLIC_GA_MEASUREMENT_IDis set at build time, mounted once in the shared root layout so it covers both the marketing/login page and every route under the demo build. The variable is set only in the build environment of the project's own deployments (the marketing/login site anddemo.opengauge.org's Cloudflare Pages project) — seeapps/web/README.md. It is intentionally absent frominfrastructure/docker/.env.example, so self-hosted Docker Compose installs never load it, keeping the existing Privacy Policy commitment (self-hosted Instances use no third-party analytics) intact. The Privacy Policy page now also discloses this usage, but only renders the disclosure when the variable is actually present at build time. - Cookie consent banner on the marketing/login page. A new
<CookieConsentBanner />(apps/web/src/components/cookie-consent-banner.tsx), translated into all four locales via the newcommon.cookieConsentmessage namespace, gates Google Analytics behind an explicit Accept/Decline choice —<GoogleAnalytics />now waits for"granted"consent (apps/web/src/lib/consent.ts+apps/web/src/hooks/use-consent.ts) before loading gtag.js on that page. A visitor who has already sent a Global Privacy Control or Do Not Track signal is opted out automatically and never sees the banner. The demo build has no login page to show a banner on (visitors skip straight to/dashboard), so it keeps loading analytics unconditionally once the env var is set, unchanged from before — a gap to revisit if the demo needs the same consent flow later.
Fixed
- Demo static export 404'd on
/dashboard(and would have on most other in-app navigation for English specifically). The same root cause as the bare-root 404 above, one layer deeper:localePrefix: "as-needed"means every locale-awareLink/redirect/useRoutercall (@/i18n/navigation) generates an unprefixed URL for the default locale (e.g./dashboard, not/en/dashboard), trusting the middleware to rewrite it — which, again, doesn't run in the static export.apps/web/src/i18n/routing.tsnow forceslocalePrefix: "always"specifically whenNEXT_PUBLIC_DEMO_MODE=true, so every generated href is explicitly prefixed and matches whatgenerateStaticParamsactually emits; the normal server build is unaffected. Two call sites were also bypassing locale-aware routing entirely —components/notification-bell.tsxandapp/[locale]/(app)/assets/page.tsximporteduseRouterstraight fromnext/navigationinstead of@/i18n/navigation— switched to match every other page/component in the app.components/user-summary.tsx(renders anywhere a user is shown in a list: activity entries, member lists, the admin users list) had the same bug with a rawnext/linkto/users/{id}, switched likewise. - Demo static export 404'd on the bare root URL.
localePrefix: "as-needed"(apps/web/src/i18n/routing.ts) relies on thenext-intlmiddleware (apps/web/src/proxy.ts) to rewrite/to the default locale on the fly — but that middleware only runs on the normal server build. The demo's static export (output: "export") has no server to run it, so the bare root produced no file at all and hosts fell through to their 404 page. A newapps/web/src/app/page.tsx(outside the[locale]segment) now covers that gap with aredirect()to the default locale, which works on any static host and is a no-op for the normal build (the middleware already intercepts/before this route would ever be reached there). apps/web/package-lock.jsonwas out of sync withpackage.json, causingnpm cito fail on any clean-install build (Cloudflare Pages included) withEUSAGE/ missing-package errors. Regenerated against a Linux Node 22 environment specifically (matching Cloudflare's build image) rather than Windows, since the drift included Linux-only optional native dependencies (@emnapi/core,@emnapi/runtime) that a Windowsnpm installdoes not resolve.
3.4.0
Added
- Granular, translated activity/audit log. Updates to assets, organizations, procedures,
locations, and (admin) users now record a real field-level diff instead of a generic action
code or a coarse whole-object dump — e.g. an asset update shows exactly which fields changed
and their old/new values, and a sensor channel's own field changes are captured too (e.g.
"channel CH1: Physical quantity — Temperature → Pressure"), not just a channel count.
app/utils/audit_diff.pyprovides the sharedsnapshot()/diff_snapshots()helpers now used across every touched update endpoint; a matched channel getschannel.<id>.<field>keys, and a wholly added/removed channel gets a single summary row instead of a per-field dump. Admin edits to another user's role, organization,is_active, oris_verified(PUT/DELETE /users/{id}) are now audited at all — they previously wrote no log entry whatsoever, despite being privilege-bearing changes. On the frontend, a new sharedActivityDiffcomponent renders these diffs — translated field labels via a newtokens.auditFieldcatalog, and translated values for enum-backed fields (physical quantity, technology, role, etc.) viatranslateAuditFieldValue— in the Activity page, the dashboard activity feed, and the asset detail page's Activity tab. AGENTS.mdgained a Granular Audit Logging policy section: every future update endpoint must capture a real field-level diff the same way, and the frontend must render it throughActivityDiff/tokens.auditFieldrather than a one-off description string.
3.3.0
Added
- Multilingual support. The web app now ships in English (canonical), Spanish, French, and
German, with the architecture designed so adding language #5+ later is a content-only change (a
LocaleMetaentry inapps/web/src/i18n/locales.tsplus amessages/{locale}/*.jsonset) — never an app-code change. Built on next-intl, with non-English locales prefixed in the URL (/es/...,/fr/...,/de/...) while English stays unprefixed at the root, so existing links and bookmarks keep working. A new globe-icon language switcher sits next to the theme toggle in the top bar; the chosen language is written to aNEXT_LOCALEcookie immediately and, once signed in, to the user's profile (PATCH /users/megained a validatedlanguagefield) so it follows a returning user across devices and browsers. Every page and shared component in the authenticated app, the login/register/forgot-password/reset-password/verify-email flow, admin/settings screens, the Privacy Policy and Terms of Service (both the app's and the marketing site's), and every enum-driven dropdown (physical quantities, sensor technologies, calibration status, roles, etc.) are translated. The activity log and audit trail translate each action (asset.created,calibration.voided, …) instead of showing the raw event code, and the dashboard calibration calendar's date tooltip shows the weekday name in the viewer's language. User-entered content (asset names, notes, audit free text) stays untranslated by design — it's shown back exactly as entered, since machine-translating someone else's data would silently change what they wrote. The separate marketing site (landing/, a static HTML/CSS/JS Cloudflare Worker project) gained its own matching language switcher next to its light/dark toggle, full Spanish/French/German copies of the marketing homepage and legal pages, and a first-visitAccept-Language-based redirect in its Worker. - Backend:
Usergained alanguagecolumn (SUPPORTED_LANGUAGES-validated, defaults toen), migrated via028_add_user_language.py, with test coverage for valid/invalid values onUserSelfUpdate. - The Knowledge Center (
apps/docs, and the same content embedded in-app at Documentation) is now translated too, using Fumadocs' built-in i18n: each guide page's Spanish/French/German translation lives as apage.{locale}.mdxsibling next to its English source, with automatic fallback to English for any page not yet translated so coverage can grow incrementally. The standalone docs site follows the same unprefixed-English/prefixed-others URL scheme as the app (/docs/guide,/es/docs/guide, …) and gained its own language switcher in the top nav. The auto-generated API Reference stays English-only in every language, since it's generated directly from the OpenAPI schema rather than hand-written.
3.2.1
Fixed
- The built-in default calibration certificate template printed a "Due Date" field
(
calibration_date+calibration_interval) unconditionally, with no way to know whether the customer had agreed to receive a calibration-interval recommendation — a direct conflict with ISO/IEC 17025 §7.8.4.3 ("A calibration certificate...shall not contain any recommendation on the calibration interval, except where this has been agreed with the customer"), and a contradiction of the certificate-generation docs, which incorrectly claimed no due-date language ever appeared.templates/certificates/default.tex.jinjano longer rendersdue_date— that cell now shows the calibration's location instead.due_dateis still passed into every template's context, so a custom uploaded template can still choose to print it (e.g. once a real customer-agreement flow exists). Found while building the Compliance section of these docs — see ISO/IEC 17025 §7.8.4.3.
3.2.0
Added
- Every place a user appears in a list — organization members, join requests, the Add Member
picker, the admin users list, and activity/audit log entries — now shows their profile picture
and links their name to
/users/{id}, via a new sharedUserSummarycomponent (UserMention, used by activity logs, is now a thin wrapper around it). Backend responses that embed a user (OrganizationMemberResponse,OrganizationJoinRequestResponse,EligibleUserResponse,AuditLogResponse) gained aprofile_picture_urlfield, resolved via a new sharedresolve_picture_urlhelper. Fixed a pre-existing gap along the way: the per-asset audit-log endpoint (GET /assets/{id}/audit-logs) never enrichedactor_name/actor_roleat all (always null) — it now shares the same enrichment as the top-level/audit-logsendpoint. - All checkboxes across the app are replaced with a new shared
ToggleSwitchcomponent — a pill-shaped switch with a smooth on/off color transition and an "On"/"Off" state label. - Login page: a password-visibility eye icon, and a "Stay signed in" toggle — checked (default)
persists the session in
localStorageas before; unchecked usessessionStorageinstead, so signing out is as simple as closing the browser. - Clicking a dashboard pie chart segment now navigates to a filtered register: calibration status → assets filtered by status, sensor/DAQ type → assets filtered by that subtype, procedure physical quantity → the procedures register filtered accordingly.
- Admin → Dashboard → Export/Import database now bundles every MinIO object (certificates,
datasheets, LaTeX templates, profile pictures) alongside the
pg_dumparchive in a single zip, so restoring a backup on a different instance brings its media along instead of leaving file references pointing at nothing. Import still accepts a barepg_dumparchive from older backups (database only, in that case). - Calibration certificate PDFs are now digitally signed with a real, PDF-native signature (PAdES) — any PDF viewer with signature support (Adobe Acrobat, Chrome, Preview) can verify a certificate was issued by its organization and hasn't been altered since, independent of which LaTeX template rendered it. Each organization gets a lazily-generated, self-signed RSA-2048 certificate on its first issued certificate, visible and downloadable from Organizations → (organization) → Certificate signing. See Certificate digital signatures for how to verify one.
- Certificate templates documentation gained an extensive "Recipes" section with copy-pasteable patterns for every placeholder shape, plus a minimal working template skeleton.
Fixed
- The user's profile picture didn't show a pointer cursor on hover even where clicking it does something (opens the preview modal, opens the top-bar avatar dropdown).
3.1.1
Fixed
- High priority: the named-volume-to-bind-mount migration command documented in
Self-hosting → Deployment (for upgrading an install that pre-dates the 3.0.0 bind-mount fix)
silently failed to copy anything when run from Git Bash on Windows: MSYS rewrites any
/-prefixed argument before it reachesdocker.exe, including the container-side paths in-v /host/path:/container/path(not just the host side), mangling them into nonsense likeC:\Program Files\Git\from.docker run ... cp -a /from/. /to/then writes into a bogus location instead ofdata/postgres/data/minio— with no error surfaced, so it looked exactly like the rebuild had wiped the data, when the real cause was the migration step never actually running against the right path. The doc now calls out running the command from PowerShell/cmd.exe instead of Git Bash, or prefixing it withMSYS_NO_PATHCONV=1if Git Bash is unavoidable, plus how to recognize (a stray emptydata/postgres;C-style directory) and recover from a botched prior attempt (the original data is still intact in the old named volume until explicitly removed).- Confirmed the current bind-mount configuration itself (introduced in 3.0.0) is not the source
of any ongoing data loss: a full
docker compose down+up --buildround-trip, both against a freshly-initialized emptydata/directory and against this repo's own populated one, preserves every row and file exactly. - Added
scripts/verify-media-persistence.sh— an isolated, non-destructive check (throwaway Compose project name and temp data directory, safe to run alongside a live deployment) that writes a marker row/object, tears the containers down, rebuilds, and confirms both survive. Exits non-zero on failure, so it can be run after any change todocker-compose.ymlor wired into CI as a regression guard against this class of bug recurring.
- Confirmed the current bind-mount configuration itself (introduced in 3.0.0) is not the source
of any ongoing data loss: a full
3.1.0
Added
- Per-user notification preferences, and a completed notification lifecycle (delete individual/all,
click-to-navigate) for the in-app inbox introduced in 3.0.0.
- New Settings → Notifications section lets each user choose, per category (calibration due, new calibration recorded, organization join request, organization join decision), whether they receive it via email, in-app, both, or neither. New users and untouched categories default to both channels enabled, so nothing needs configuring out of the box.
- The notification bell dropdown gained a Clear all action and a per-notification remove (×) button — previously the inbox could only be marked read, never actually cleared.
- Calibration due-soon/overdue reminders and new-calibration notifications now raise an in-app notification the same way organization join requests already did (in 3.0.0), instead of only ever emailing — the in-app channel doesn't depend on SMTP being configured at all. Both link to the asset's detail page.
- The calibration reminder sweep now marks a reminder as sent once any enabled channel delivers for any recipient (in-app or email), rather than requiring email specifically — a broken SMTP server no longer blocks the guaranteed in-app channel, and won't be retried forever once the recipient has been notified some other way.
3.0.0
Added
- Organizations are now full multi-tenant entities. Any non-Viewer user can create one and becomes
its first admin automatically; a user can belong to any number of organizations, each with its
own
member/adminrole — distinct from the global RBAC role on the user's account. Only Super Admin overrides into organization management the caller isn't a member of; the globaladminrole has no special access to organizations, matching the peer-to-peer model. Viewer is blocked from all organization management regardless of any per-orgadminrole they might hold — the global RBAC restriction wins over the org-level one.- New profile fields:
full_name,website,location_id(primary/HQ location),email,phone,private. - New dedicated Organizations page (list + per-organization detail page, replacing the old Admin → Organizations panel) and a sidebar tab.
- Members can leave, and admins can remove members or change their role — guarded so an organization is never left with zero admins (the last admin must promote someone else first, or deactivate the organization instead).
- Private organizations show only their name (with a lock icon) to non-members, so they stay discoverable enough to request joining; everything else about them — profile, member roster, assets — is members-only.
- Non-members can send a join request; every admin of that organization gets notified via
a new in-app notification inbox (bell icon in the top bar) always, plus email if SMTP is
configured. Approving adds the requester as a
member. - Assets now carry a direct
organization_id(chosen from the creating user's own organizations), independent of the asset's location — shown on the asset detail page and linked back to the organization's page, which in turn shows a clickable asset count filtering the asset register. - The organization page's Members panel gained an Add member button (a modal listing every
user not already an active member, multi-select, added with the
memberrole) and a Danger zone with a confirmed delete action — deleting deactivates the organization, hiding it from everyone except Super Admin. Pending join requests now show inline in the same Members list with a "Pending" status instead of a separate panel, and approving/rejecting doesn't require edit mode; changing a member's role or removing them now does. - A Join / Leave button now appears on every organization's list row and its detail page (beside Edit for admins, in Edit's place for everyone else): "Request to join", a disabled "Request pending" once a request is in flight, or a red "Leave" for members — each behind a confirmation dialog. Leaving as an organization's last admin is guided instead of just rejected: promote another member first, or delete the organization if it's the last member too.
- New shared
ImageUploadFieldUI component (circular picture, click-to-preview, overlaid upload/remove buttons in edit mode) — now used consistently for the organization logo, asset picture, and user profile picture. - A deactivated organization is now visible in the Organizations list to Super Admin only,
shown with a red background and a "Deleted" badge, and can be reactivated with a "Restore
organization" button in its edit form's Danger zone (
POST /organizations/{id}/restore) — restoring only affects visibility, not membership.
- New profile fields:
Changed
- Breaking: Viewer is now read-only across the whole app, not just organizations — they can no
longer create, edit, retire, import, or export assets, procedures, or locations. The
require_not_viewerdependency (already used for signature management) now also gates every mutating endpoint onassets,procedures, andlocations; the corresponding frontend New/Edit/Delete/Import/Export controls are hidden for Viewer accordingly. - Breaking: Teams are removed. Open Gauge now mimics Gogs — only users and organizations,
no team layer between them.
- Dropped the
teamsandteam_memberstables, the/teams*API endpoints, the Settings → Teams self-service join/leave UI, and the Admin → Organizations nested team panel. - Asset ownership (previously
assets.owner, a team reference) is superseded by the directassets.organization_idabove. - Calibration email notifications (new-calibration and due/overdue reminders) now go to every active Technician/Admin/Super Admin who is a member of the asset's organization instead of a team's members; Viewers are never notified. The built-in certificate template's footer drops its separate "Team" line, since the "Organization" line already covers this.
- Dropped the
- Breaking: The redundant
User.is_superuserboolean is removed. It was always ORed withrole == "superadmin"at every permission check; Super Admin capability now comes solely from therolefield. Any account that hadis_superuser=trueis promoted torole=superadminby the migration so no one loses access. - Breaking:
users.organization_id(a single nullable FK, one org per user) is replaced by theorganization_membersmany-to-many table described above. - Consolidated 11+ duplicated
_require_admin/_require_superuserrouter-local helper functions into sharedrequire_admin/require_superadminFastAPI dependencies independencies/deps.py. The frontend's four copies ofROLE_LABELS/ROLE_COLORSare similarly consolidated intolib/roles.ts.
Fixed
- The organization logo displayed as a non-clickable square instead of the circular, click-to-preview picture used everywhere else.
- An organization's website link resolved as relative to the current page when the stored value
had no
http(s)://prefix, sending visitors to a broken/organizations/<website>URL instead of the intended site. Storage is unchanged; only the outgoing link is normalized. - The asset count on an organization's page linked to the asset register but didn't actually
filter it by organization — the register never read
organization_id/organization_namefrom the URL. - High priority: uploaded files and database rows could still appear to be wiped after
docker compose up --buildeven with the project name pinned (previous fix) — a different invocation (cwd,-poverride, or a legacydocker-composev1 binary ignoring thename:key) could still resolve to a differently-named volume. Postgres and MinIO now bind-mount to a fixed host path (infrastructure/docker/data/) instead of a named volume, which can't diverge this way. Existing installs: copy your current named-volume data into./data/postgresand./data/miniobefore upgrading (docker run --rm -v <old_volume>:/from -v $(pwd)/data/X:/to alpine cp -a /from/. /to/), then redeploy. - The organization list page's logo was square instead of the circular treatment used everywhere else.
- The organization picture stayed small (48px) while editing instead of matching the bigger size used when editing an asset or profile picture.
- Landing on the asset register from an organization's (or location's) filtered link didn't apply
the filter until the page was manually refreshed — the register read the filter from
window.location.searchin a mount-only effect, which client-side navigation doesn't re-trigger. Switched to the reactiveuseSearchParams()hook. Separately, dismissing a filter via "View all" only cleared in-memory state, not the URL, so a refresh afterward silently re-applied it — "View all" now also clears the corresponding URL params. - The organization page's "Add member" button was visible outside of edit mode, inconsistent with every other member-management control.
2.4.0
Added
- The signature pad (used when a user sets their approval signature) now has Undo and Erase buttons alongside Clear. Undo reverts the last stroke; Erase toggles an eraser mode that removes ink under the pointer instead of adding it, so a mis-drawn portion of a signature can be corrected without redrawing the whole thing.
- A new 1×0.5 in QR sticker size, alongside the existing 2×2 and 4×2 sizes: QR code on the left half, asset ID and asset name on the right half. Available in PNG, JPG, and PDF from the same asset Sticker dialog.
2.3.0
Changed
- Changelog entries (here and in the repo root's
VERSIONS.md) now show only the version number, not a date — git history is already the record of when something shipped, and the two dates could drift.
Fixed
- The calibration reminder sweep could send real emails during local development and test runs.
email_settingswas documented as a singleton but never enforced as one at the database level; code that queried it with an unordered.first()could silently pick up whatever row existed, including a real, already-configured SMTP row, so a test exercising the reminder sweep could end up delivering a genuine email instead of a no-op. A unique index now makes a second row impossible to create, the same technique already used forcertificate_templates. - The Dangerous zone's Clear database action could delete the account that triggered it.
The access check accepts either the
is_superuserflag orrole == "superadmin", but the reset only preservedis_superuseraccounts — arole == "superadmin"user without that flag could reach the action and then be wiped by it. The calling user is now always preserved regardless of role or flag.
2.2.0
Changed
- Correction to 2.1.0: the landing/marketing page content (features, comparison table, FAQ,
contact form) never belonged in this repository — it's the public
opengauge.orgsite, which lives in a separatelandingrepository. Revertedapps/web/src/app/page.tsxand removed theapps/web/src/components/landing/andapps/web/functions/directories added in 2.1.0; that content was rebuilt in thelandingrepo instead, matching its plain HTML/CSS/JS stack. - Simplified the admin Dangerous zone: removed the "clear selected tables" action added in 2.1.0 (an admin-only endpoint with no UI to grant its own precondition wasn't worth the complexity) and fixed the panel's styling to match the rest of Admin → Dashboard — Import now sits in the same red "Dangerous zone" card as Clear (both genuinely destructive), while the non-destructive Export stays in its own neutral Backup card.
Fixed
POST /admin/database/importfailed withpg_restore: error: could not execute query: ERROR: unrecognized configuration parameter "transaction_timeout". Debian's defaultpostgresql-clientpackage is newer (17) than thedbservice'spostgres:15-alpineserver, and pg_restore synthesizes session-setup statements based on its own client version — v17's aren't understood by a v15 server. The API image now pinspostgresql-client-15via the official PGDG apt repository, matching the server exactly.- Uploaded files (profile pictures, signatures, PDFs, certificate templates) and database rows
could appear to be wiped after
docker compose up --build. Docker Compose derives its project name — and therefore its volume names — from the current directory by default, so invokingdocker composefrom a different location (or a different way) than usual silently creates a second, differently-named stack with fresh, empty volumes.docker-compose.ymlnow pins an explicit projectname, so the same volumes are always used regardless of how Compose is invoked.
2.1.0
Added
- The landing page (
opengauge.org) is now a full marketing page: a refreshed features section, a feature comparison against legacy on-premise calibration software / spreadsheets / generic cloud CMMS, an FAQ, and a contact form to[email protected]. - A Cloudflare Pages Function (
apps/web/functions/api/contact.ts) handles the contact form's submissions via Cloudflare's own Email Routing "send email" binding — no third-party email API or extra dependency. Only active on the Cloudflare Pages deployment; seeapps/web/functions/README.mdfor the one-time Cloudflare-side setup. Everywhere else (self-hosted Docker, local dev) the form falls back to amailto:link.
Fixed
- The admin Dangerous zone (database export/import/reset) was invisible to any account
promoted to the
superadminrole after the initial install, because the check required the separateis_superuserflag — which no admin-panel UI can ever set on another account. It now also acceptsrole == "superadmin", matching the same convention already used everywhere else in the API for this privilege tier.
2.0.0
Changed
- Breaking: Team membership is now opt-in. Previously the
User.teamfield was a single free-text string with no real membership model behind it; it's replaced by ateam_membersjoin table, so a user can belong to any number of teams and starts in none. Theteam/teamsfield changed shape on the user API (UserResponse.team: string | null→UserResponse.teams: {id, name}[]), andUserCreate/UserUpdate/UserSelfUpdate.teamwere removed — join/leave a team viaPOST/DELETE /teams/{id}/join|leaveinstead. Existingusers.teamvalues are carried over into real membership rows by the migration wherever the text matched a team in the user's own organization. - Creating, renaming, and deleting teams is now only possible from Admin → Organizations. The user's own Settings → Teams tab is self-service only: pick which of your organization's teams to join or leave.
Added
- Dangerous zone moved into Admin → Dashboard (superadmin only): alongside the existing export/import/reset actions, a new Clear selected tables action lets you pick exactly which database tables to empty, rather than only an all-or-nothing reset.
- The PDF preview thumbnail (asset Files section, certificate templates) now shows an eye icon and shadow on hover, to signal it's clickable.
Fixed
- The signature drawing pad's background no longer goes near-black in dark mode, which previously made the dark-ink signature invisible while drawing (and in the saved-signature preview).
1.2.0
Added
- Ratiometric output signal type for sensor channels (e.g. bridge-type load cells), with
mV/VandV/Vunits. See Output signal. - PDF preview for PDF files in the asset Files section — click a file's thumbnail to preview it before downloading, using the same previewer as calibration certificates.
- Reference tables in Adding a sensor covering every supported physical quantity (with technology, type, and units) and every output signal type (with units).
Fixed
- The certificate template dropdown menu text is now visible in dark mode.
- The asset's profile picture no longer appears in the asset's Files list — it's managed only from the Image section, avoiding duplication.
1.1.0
Added
- Database panel (Admin → Database, superadmin only): export a full database backup, restore one, or reset the app to a clean state (deletes all data except superadmin accounts).
Changed
- Fresh installs now start empty. The first account registered on a new install is created
verified and as
superadminautomatically — previously it was created unverified with no admin able to activate it, and the app shipped pre-populated with demo users, assets, locations, and procedures instead of starting empty. See Authentication and Email notifications. - Docker Compose now reads every credential and URL from
infrastructure/docker/.envinstead of hardcoding them indocker-compose.yml— see Docker Compose deployment. - Generated QR codes and asset labels now encode the configurable
FRONTEND_URLinstead of a hardcodedhttp://localhost:3000, so they resolve correctly in production.
Fixed
- The sidebar's "Documentation" section no longer auto-expands when viewing an API Reference page — only clicking "Documentation" itself expands it.
1.0.13
Added
- PDF handling and download for generated calibration certificates.
1.0.12
Added
- User signature management: upload, retrieval, and cryptographic verification.
- LaTeX rendering service and certificate template management.
1.0.11
Fixed
- Logo image dimensions on the sidebar, for proper scaling in light mode.
Added
- Additional documentation links for sensor attributes and asset registry fields.
1.0.10
Added
- Demo mode: in-memory data store with session persistence, for trying Open Gauge without a backend.
Changed
- General functionality and performance improvements across the app.
1.0.9
Added
- Password reset flow and account activation for self-registered users.
Changed
- Cleaned up
package-lock.json, removing unnecessary peer dependencies.
1.0.8
Added
- Dark and light logo SVGs, a new app icon, a calibration-method badge component, and the dark/light theme toggle.
- Radial-gradient backdrop treatment for the dashboard grid background.
1.0.7
Added
- Calibration soft-void functionality (void a calibration without deleting its history).
Changed
- Production start script switched to a custom static server for the Next.js export build.
1.0.6
Added
- Email notifications feature, with SMTP configuration in the admin panel.
1.0.5
Changed
- Broader code-structure refactor for readability and maintainability.
- Removed unnecessary peer dependencies; added
@emnapi/core/@emnapi/runtime.
1.0.4
Added
- Asset import via ZIP upload.
1.0.3
Changed
- Removed outdated standalone
ARCHITECTURE.md,DATABASE.md, andUNITS.mdnow that this documentation site is the source of truth.
1.0.2
Added
- Profile and asset picture uploads.
1.0.1
Changed
- Dashboard component styling and functionality improvements.
- Housekeeping: compiled Python artifacts for storage/utility modules.
1.0.0
The first release considered production-ready: licensed, documented, and covering the full calibration-to-certificate workflow.
Added
- Knowledge Center + API Reference documentation site (this site), with
fumadocs-openapigenerating the API Reference straight from the FastAPI schema. - AGPL-3.0 license, and an expanded
README.md/CONTRIBUTING.md.
0.5.8
Added
- Support for coefficients-only external calibrations (record a calibration without raw point data, using previously-derived coefficients).
0.5.7
Added
measurement_typefield on sensors, with related UI updates.
0.5.6
Added
- Calibration worked examples, an uncertainty-budget breakdown, and reporting utilities.
0.5.5
Added
- Health scoring: health service, scoring tests, and the asset Health tab.
- Health overview display and an enhanced calibration ring card visualization.
0.5.4
Added
- Calibration deletion, restricted to admins.
0.5.3
Changed
- Asset and audit log handling enhanced with actor-based filtering; sensor update fixes.
0.5.2
Changed
- User and audit log detail views now surface actor information (who performed the action), with general UI component improvements.
0.5.1
Added
- Procedures and procedure-distribution summary on the dashboard.
0.5.0
Added
- Pie chart components, with hover effects and shared context management.
Changed
- Calibration lab location retrieval integrated into the Calibration tab, with UI updates for calibration lab display.
0.4.4
Changed
- Dashboard types and API extended with calibration status and recent-assets data.
0.4.3
Added
- Sticker modal for label generation, with preview and download options.
Changed
- Color definitions reorganized; label generation logic cleaned up.
0.4.2
Added
is_calibration_labflag on locations and acalibration_location_idlink on calibrations.
0.4.1
Changed
- Asset and procedure schemas refactored; improved form validation and error handling.
0.4.0
Added
- Label/sticker generation service.
- Activity log.
0.3.7
Added
- Admin panel.
- Calibration report PDF generation.
0.3.6
Added
- User settings page.
Fixed
- Small issues in the calibration wizard and calibration view.
0.3.5
Added
- Procedures page, with file uploads.
- Procedure editing.
0.3.4
Added
- Support for adding new assets.
Fixed
- Asset editing behavior.
0.3.3
Changed
- Calibration chart replaced with Plotly.
- Calibration view updated to support the new calibration graph/table layout.
0.3.2
Added
- Calibration record add workflow.
Changed
- Further location editing refinements.
0.3.1
Added
- Add/edit workflows for locations.
- Asset details edition improvements.
0.3.0
Added
- Locations page, with a site/building/lab hierarchy.
Changed
- Sidebar/topbar background graph styling.
0.2.3
Changed
- Database-backed overview tab panels updated.
Added
- Edit asset overview feature, with initial tests.
0.2.2
Added
- Asset profile page.
0.2.1
Changed
- Assets registry table updated.
0.2.0
Added
- Dark/light mode.
Changed
- Dashboard rebuilt against the new database schema, with new panels.
0.1.2
Changed
- UI elements harmonized across early screens.
0.1.1
Added
- Profile loading on authentication.
0.1.0
Added
- Authentication page and login logic.
- Dashboard screen.
0.0.0
- Initial commit: monorepo scaffolding and base architecture (Next.js frontend, FastAPI backend, Docker Compose infrastructure).