* Lightbox: Add 360° photo and video sphere viewer support
Wires Photo Sphere Viewer v5 into the lightbox for equirectangular
media. Backend detects UsePanoramaViewer and IsPhotosphere markers
alongside ProjectionType, and exposes Panorama + Projection in the
viewer / search result DTOs so the frontend can dispatch on the
projection type. The renderer (ThreeJS + PSV core + video plugin +
equirectangular video adapter) is lazy-loaded into a dedicated
webpack chunk so the base bundle is unchanged for users with no
360° media. PhotoPrism's existing lightbox controls drive the
underlying HTMLVideoElement that the PSV adapter creates for 360°
videos. Includes Vitest coverage for the sphere helper and the
lightbox dispatch, plus a TestCafe smoke spec and three new
ExifTool metadata fixtures.
* Lightbox: Fix 360° controls leak, hide zoom button, add sidebar icon
* Lightbox: Fix 360° touch panning and navigation arrows
* Lightbox: Fix fast swipe switching photos on 360° slides
Suppress PhotoSwipe swipe/drag navigation at its source via the dispatched pointer hook while a sphere slide is active, since the per-element gesture trap is outrun by a fast swipe whose pointer leaves the sphere container (touch-capable Windows). UI controls stay excluded so buttons and arrows still navigate.
* Lightbox: Route only equirectangular media to the 360° viewer
Routing was based on the loose photo_panorama flag for videos, which is also
true for cubemap and merely-wide (aspect > 1.9) videos, so non-equirectangular
media wrongly opened, distorted, in the sphere viewer. Route strictly on the
equirectangular projection for both photos and videos. Surface the video file's
projection in the viewer DTO via Photo.MediaProjection() (the primary search row
is a poster JPEG with no projection). Add an MP4 metadata-detection test.
* Lightbox: Open tagless 2:1 360° videos in the sphere viewer
Many 360° videos carry no projection tag PhotoPrism can read, so strict
equirectangular-projection routing left them in the flat player. Fall back to
equirectangular's defining 2:1 frame for panorama-flagged videos without a
projection, via is360Equirectangular() in common/sphere.js, while cubemap and
ultrawide (~2.35:1) clips stay flat.
* Tests: Make the 360° sphere video test assert instead of skipping
Bump abema/go-mp4 to v1.7.1 (only outdated direct dependency) and tidy
go.sum, pruning stale checksums left by the XMP sidecar merge. Regenerate
the license NOTICE for the updated dependency.
Use US-English spelling in the new XMP sidecar comments and tests, and
drop a specs/ reference from internal/meta/README.md so the public package
doc has no link into the private subrepo. Regenerate dialect_mysql.go from
SQL.
A "near=<uid>" geo search set the S2 cell but left the form lat/lng at
zero, so results sorted by distance from the (0,0) origin instead of
the referenced picture. Use the picture's own position in the ORDER BY,
falling back to time order when it has no usable coordinates.
Past the cluster zoom threshold, group un-clustered photo features by
projected screen distance instead of exact coordinates, so pictures at
the same place stack into one counter marker even when their stored
coordinates differ slightly (GPS jitter, tile quantization). Key each
stack by a stable member UID and open it by its bounding box so the
panel lists exactly those pictures.
Stop deriving a file's OriginalName from cached metadata during indexing.
The ExifTool JSON cache is keyed by content hash, so a renamed file was
re-indexed against the old name and surfaced it as OriginalName, making
cards show a stale name while the sidebar showed the current one.
OriginalName is now set only from the explicit import original name, so
plain-indexed files no longer carry one and imported files are unaffected.
Add pkg/txt.JoinOr plus acl.RolesCliUsageString and UserRoleUsageFor so the users/clients --role and --auth help derive from the role tables instead of hand-maintained literals, filtering the app/uploader aliases and visitor. Each edition lists its own roles; federatable contexts (LDAP, OIDC group-role, cluster grants) use ClusterInstanceRoles.
PActionMenu reads $util.shouldOpenOnHover() in data(); the suite only
passed because PActionMenu was stubbed. Complete the $util mock so the
tests no longer depend on that stub to hide the call, which otherwise
fails with "this.$util.shouldOpenOnHover is not a function".
Naming faces in quick succession left the second new name out of the
people type-ahead until a manual reload, because eviction relied on the
subjects WS event, which can lag the next focus-triggered read.
Marker.setName() and Face.setName() now seed the shared people cache with
the saved name so it is suggestible immediately; the rename path seeds via
recognized.vue (Subject.update() also backs hide/show, so it can't host the
seed). Harden the cache itself: get() snapshots an epoch so a fetch that
resolves after an evict/upsert no longer clobbers the slot with stale data,
and upsertPeople merges idempotently. Reload new.vue suggestions on focus
to match the photo people tab.
Group un-clustered geo features by exact coordinates so that pictures
sharing a spot render as one stack marker with a counter instead of
overlapping, hidden duplicates once zoomed past the cluster threshold.
Clicking such a stack opens the location's collection panel rather than
a radius search that surfaced unrelated nearby photos.
Key the embedded photo list by latlng so an already-open panel re-runs
its search for the newly selected cluster or stack.
Only warn when a request presents a token that is rejected; requests
without any credentials (e.g. the web app loading public config before
login) are routine and now logged at debug level, reducing log noise.
- Add scoped session revocation (RevokeSessions / RevokeDerivedSessions)
shared by the user-update API and the CLI: a privilege-level change now
revokes logins and app-password-derived sessions while keeping the app
passwords themselves, so configured devices keep working.
- Identify app-password-derived sessions by auth_method "session".
- Deny app passwords on the REST API when the account cannot log in
(DenyLogIn); WebDAV access stays governed by CanUseWebDAV.
- Reduce the WebDAV auth cache expiration to one minute.
- Add IsSystemOrInvalid; allow deleting the initial admin account and skip
re-initializing a deactivated or deleted admin in InitAccount.
- Move session-revocation scopes to pkg/authn and extend tests.
Guard recognized.vue onSave against re-submission while the people cache
resolves by setting busy before the async lookup. Drop updatePersonList so
Browse Pictures / Set Cover no longer evict and refetch the whole people
list; the name combobox reloads suggestions on focus and WS events keep
the cache current. Make the people-tab test seed via a mocked getPeople.
Move the people name list out of the client config into the shared
typeahead cache (getPeople, backed by GET /api/v1/subjects), matching
labels and albums. Consumers load suggestions on demand and the cache
evicts on people.* / subjects.* events and clears on logout; the
config.js people machinery is removed and ClientConfig keeps only
count.people for the navigation badge.
Groundwork for the people type-ahead cache: include the people list and
count in the client config only for roles that may view or search people,
and omit the list from the global config.updated broadcast. Clients load
it via GET /config and keep it current through people.* events.
helperAfterEach reverted photos before restoring albums, so re-adding a
photo to an album a test soft-deleted targeted a dead UID, 404'd, and
cascaded into helperFailures. Determine album changes first so the photo
loop can skip albums revertAlbums restores (recreated foreign-owned ones
get a new UID), and tolerate 404 on add-to-album.
Make the cameras/lenses/countries content-channel events publish only a
stable identity ([]string of slug or ISO code) instead of full entity
models, matching the UID-only invariant of every other content channel.
The update path no longer republishes count.* (an edit does not change
the count).
Restore surgical people updates: onPeople refetches only the affected
people by UID via Subject.search (refetchPeople) and upserts them in
place instead of forcing a full client-config reload (a #1307 regression).
Document the dormant folders.*/faces.* subscribers (no publisher, not
forwarded) and fix the misleading payload example in the event README.
Pin axios 1.18.0 (security-hardening minor: strips sensitive headers on cross-origin redirects, rejects malformed http/https URLs, tightens prototype-pollution defenses; no breaking changes). Bump vitest and coverage-v8 to 4.1.9, playwright to ^1.61.0. OSV-Scanner clean for both ecosystems.
GetDest() lazily wrote the shared ImportSettings.Dest on every call, so
parallel ImportWorker goroutines raced on it. Make GetDest side-effect
free and normalize an invalid stored pattern once in Settings.Propagate.
Also align the per-hash indexing lock added in #5652 with code style.
Mirror the lens make/model editing surface for cameras: entity UpdateMakeModel/SaveForm, form validation, query, search, the GET/PUT /api/v1/cameras endpoints, and the cameras CLI command, plus a cameras ACL resource and scope.
Also tidy the lens surface for parity: self-validating SaveForm, empty make/model guard, X-Count search header, service-role grant, the empty-id/slug docs, and order cameras before lenses everywhere.
Adds the ability to override a lens's Make/Model (e.g. fixing Pentax lenses that ExifTool decodes as `4 38`) via a new photoprism lenses update CLI command and a `PUT /api/v1/lenses/:id endpoint`, plus a `GET /api/v1/lenses` search endpoint, a new lenses ACL resource, and an lenses ls list command.
Adds four selectable app-icon variants with optimized SVGs and full PNG size sets. bloom and ring use a round canvas like logo; flower and shutter use the rounded-square canvas. Updates the frontend icon resolver, app-icon help text, and config tests, and strips editor data-name attributes from the existing icons.