Commit Graph
62 Commits
Author SHA1 Message Date
iu b7316f9e00 Merge commit '327f820' into develop 2026-10-13 12:41:16 +03:00
iu 327f820c3b chore(openspec): archive add-mobile-photo-zoom and sync main specs 2026-10-13 12:41:04 +03:00
iu beeb4c7c09 feat(viewer): show a loading spinner while the zoomed original loads
Show a spinner in the top-right of the photo viewer while the original is
being loaded on zoom, so the wait is visible. Only the original triggers it;
the fitted preview and a re-shown preview never do.

Cover the visibility gate with a unit test.

OpenSpec change: add-mobile-photo-zoom
2026-10-13 12:40:53 +03:00
iu 13d5e0f290 Merge commit 'f6df9e2' into develop 2026-10-13 12:35:21 +03:00
iu f6df9e23f2 feat(viewer): add pinch-to-zoom for photos on Android
Add pinch/pan/double-tap zoom to the full-screen photo viewer, mirroring the
web viewer semantics:

- scale is a multiplier of the natural size, capped at 200%; the fitted view
  is the lower bound
- double tap toggles fitted view <-> 100% natural anchored at the tap
- the original loads on zoom, with the preview kept as a placeholder so
  entering zoom never flashes a black surface
- the pager is disabled while zoomed (drags pan the image) and the zoom resets
  when leaving a photo
- video and 360 panoramas are unaffected

Implement PhotoZoom (zoom math) and ZoomableImage (gestures), wire it into
ViewerContent/MediaPage, and cover it with unit, Compose and device tests.

OpenSpec change: add-mobile-photo-zoom
2026-10-13 12:30:49 +03:00
iu 28828f1dcd chore(openspec): archive add-make-install-android and sync main specs 2026-10-13 12:06:59 +03:00
iu d9f6a163e5 feat(make): add install-android-app target and default help listing
- add `help` target and make it the default goal (bare `make` lists targets)
- add `install-android-app`: build debug APK and install on the selected device
- device selection via SERIAL/$ANDROID_SERIAL; several devices list and stop
- document the target in android/README.md
2026-10-13 12:06:34 +03:00
iu 43f5520236 chore(openspec): archive add-user-avatar and sync main specs 2026-10-12 16:17:40 +03:00
iu 04617d1cdb feat(profile): add round avatar with upload and cropping
- backend: user_avatar_objects binding (PG+SQLite), GC preserves avatar blobs,
  avatar service (EXIF-stripping WebP 128/512), public avatar read route
- web: avatar upload/crop on profile, round avatar in header/members/comments/author
- android: avatar upload/crop, round avatar on profile and albums header
- openspec: add-user-avatar proposal/design/specs/tasks
2026-10-12 16:16:47 +03:00
iu 657a04309e chore(openspec): archive add-user-profile and sync main specs 2026-10-12 14:27:19 +03:00
iu 86aff695df feat(auth): add user profile, phone login, and account deletion 2026-10-12 14:26:32 +03:00
iu 1cb9aaf198 chore(openspec): archive fix-trash-counter-sync and sync main specs 2026-10-12 12:58:34 +03:00
iu 713c1ca6d3 fix(ui): keep trash counter in sync with trash changes
Reload the rail trash count after every operation that changes the
trash contents, not just navigation and empty-trash: moving photos to
trash (grid, panel, viewer), restoring, permanent delete and deleting
an album. Previously the counter stayed stale until the next route
change.
2026-10-12 12:58:18 +03:00
iu bf3641f9e0 chore(openspec): archive add-android-app and sync main specs 2026-10-12 12:15:13 +03:00
iu 9e85e52a73 chore(openspec): archive fix-photo-tag-sync and sync main specs 2026-10-12 12:15:08 +03:00
iu 3666f4ea6d chore(openspec): archive host-agnostic-object-urls and sync main specs 2026-10-12 12:15:03 +03:00
iu aa2f7552bc feat(android): add native Android client
Kotlin + Jetpack Compose client for a self-hosted instance: manual
server setup and health check, auth with Keystore-backed tokens, album
and photo browsing with SSE live updates, upload queue in WorkManager
with HEIC-to-JPEG conversion, and Room/Coil offline cache. Add an
android Makefile target and revise project.md §17.
2026-10-12 12:14:54 +03:00
iu e4975aafb5 fix(ui): propagate photo tag edits to grid and viewer
Add a shared photo-updates signal the panel publishes on rename or tag
add/remove; the album grid and viewer subscribe and patch the changed
photo into their lists, so the 360 badge and panorama mode update without
a reload.
2026-10-12 12:14:48 +03:00
iu eef7c6c5d0 fix(storage): emit origin-relative object URLs
Build signed object URLs as path-only (/api/objects/...) instead of
prefixing APP_BASE_URL, so media loads from whatever host or reverse
proxy served the app. Remove the now-unused base URL from the signer and
drop the APP_BASE_URL / Server.BaseURL config.
2026-10-12 12:14:46 +03:00
iu 4d0e24347c chore(openspec): archive fix-pano-drag-direction and sync main specs 2026-10-11 21:02:06 +03:00
iu 6528abad82 fix(ui): correct horizontal drag direction in 360 panorama viewer
The panorama viewer inverted horizontal drag relative to vertical: the
yaw sign was opposite, so dragging right turned the view right instead
of moving the scene right. Flip yaw to match the pitch convention
(drag the world) and lock the behavior with a ui-workspace scenario.
2026-10-11 21:02:01 +03:00
iu a72d8876d6 chore(openspec): archive trash-permanent-delete-and-empty and sync main specs 2026-10-11 20:55:04 +03:00
iu 3b745fa737 feat(photos): fix trash permanent delete and add empty trash
Permanently deleting a photo whose album was deleted failed because
authorization resolved through the album. Fall back to uploader
ownership when the photo is albumless. Add a bulk DELETE /trash that
removes every trashed photo the caller may delete (uncapped listing)
and returns the deleted count, with an «Очистить корзину» action on the
trash screen.
2026-10-11 20:54:54 +03:00
iu b2e0dd6d30 chore(openspec): archive add-video-support and sync main specs 2026-10-11 20:38:22 +03:00
iu d38ede0201 feat(video): add video upload with remux/transcode playback
Add video as a media kind in photos: streaming upload with per-kind
limits, ffprobe metadata, poster frame reusing the image variant
pipeline, and a playback object (remux when web-compatible, else
h264/yuv420p/aac transcode). Video jobs run on a separate pool with a
long lease. Frontend gains a video viewer, grid badge/duration and
video upload.
2026-10-11 20:37:53 +03:00
iu d059d042f0 chore(openspec): archive thumbnail-variants and sync main specs 2026-10-11 19:57:12 +03:00
iu f1b3b01bdf feat(photos): add square medium variant for sharp grid thumbnails 2026-10-11 19:57:00 +03:00
iu 581e479f03 chore(openspec): archive thumbnail-density-control and sync main specs 2026-10-11 18:24:59 +03:00
iu 511de4ec9c fix(ui): use minus/plus icons for thumbnail size step buttons 2026-10-11 18:24:35 +03:00
iu 41e5dfaeb1 feat(ui): fix thumbnail size slider direction and add step buttons 2026-10-11 18:21:47 +03:00
iu c2b0d9c66f chore(openspec): archive viewer-thumbnail-hover and sync main specs 2026-10-11 18:10:07 +03:00
iu a7867054a4 feat(ui): hover zoom on viewer thumbnail strip
- Thumbnails scale their image on pointer hover and keyboard focus
- Tile size and strip layout stay unchanged
2026-10-11 18:09:54 +03:00
iu 83cc178e79 chore(openspec): archive mobile-tap-open-and-panel and sync main specs 2026-10-11 17:58:30 +03:00
iu ebae341c9a feat(ui): open photo on tap for touch and fix narrow-screen panel
- Touch/pen tap opens the viewer; mouse click still selects, dblclick opens
- Panel and rail emit a single transform utility so the panel actually
  slides in below 1280px (Tailwind v4 utility ordering made the base
  translate-x-full win over the conditional translate-x-0)
- Add touch-action: manipulation to grid cells
2026-10-11 17:58:04 +03:00
iu 9b7d81d8fc chore(openspec): archive pano360-viewer and sync main specs 2026-10-11 17:41:54 +03:00
iu fc4233cd57 feat(ui): 360 panorama viewer by pano360 tag
- Detect 'pano360' tag and open the photo in a spherical viewer
- Mini-WebGL equirect viewer (no deps): drag to look, wheel FOV, Space reset
- Load original as origin-clean bitmap; fall back to flat image without WebGL
- 360 indicator in viewer and badge on grid tiles
2026-10-11 17:41:28 +03:00
iu 3311de57c5 chore(openspec): archive grid-keyboard-navigation and sync main specs 2026-10-11 14:51:44 +03:00
iu 7359495a73 feat(ui): keyboard navigation and focus restore in album grid
- Cursor separate from selection; arrow keys move it, Space/Shift+Arrow/Enter/Esc act
- Click on empty grid space clears selection back to album properties
- Focus returns to the last viewed photo after closing the viewer
2026-10-11 14:51:00 +03:00
iu d99ac77587 chore(openspec): archive completed changes and sync main specs
- fix-large-album-load -> photos, realtime, media-objects
- fix-album-cover -> photos, albums
2026-10-11 14:16:38 +03:00
iu d7a37328f0 fix(photos): faster large-album loading and album cover lifecycle
Large albums opened slowly and flickered; the default album cover was
also lost in the media-objects refactor.

Large-album loading:
- quantize signed object URLs within a window and send Cache-Control so
  previews are reused instead of re-fetched
- publish photo.updated (not photo.created) when variants are ready, so
  clients update in place without reordering the grid
- worker concurrency + newest-first variant priority; raise default batch

Album cover:
- first uploaded photo becomes the cover when none is set
- clear the cover when the cover photo is trashed or moved out
- "set as album cover" in the photo context menu and actions panel

OpenSpec changes: fix-large-album-load, fix-album-cover
2026-10-11 14:15:59 +03:00
iu 6361e48b20 feat(observability): configurable logging, request ids and error logs
- internal/logging: LOG_LEVEL/LOG_FORMAT parsing and slog handler
- request_id middleware (X-Request-ID or generated, echoed) + debug access log
- httpx registers errors; middleware logs internal causes with request_id
- health excluded, objects/sse debug-only, no query/body/secrets in logs
- new observability capability spec; README/.env.example docs
2026-10-11 13:40:11 +03:00
iu 36e96cd519 feat(seed): add API test-data seeding script and make testdata target
- scripts/seed.py: stdlib HTTP client, JPEG+EXIF via optional Pillow (PNG fallback), additive-only, dry-run
- Makefile: testdata target with ALBUMS/PHOTOS/SEED/TIMEOUT and boolean flags
- README: usage section; new dev-seed-data capability spec
2026-10-11 13:27:21 +03:00
iu b1b0b4a83e chore(openspec): archive completed changes and sync main specs
- archive rename-shared-album-to-galo, add-halo-branding
- sync specs: ui-branding (new capability)
2026-10-11 11:37:23 +03:00
iu 50dff63ece feat: rename service to galo, add halo branding and release build
- rename Go module, binary, systemd unit, PG_DATABASE default, frontend package and docs from shared-album to galo
- add theme-aware halo emblem and favicon (new ui-branding capability)
- add make build-release (versioned, stripped, static binary) and run the binary from the repo root
2026-10-11 11:36:56 +03:00
iu 8638d1bbfc chore(openspec): archive completed changes and sync main specs
- archive local-fs-storage-backend, sqlite-metadata-backend,
  storage-orphan-reconciliation, metadata-managed-jwt-secret, default-local-backends
- sync specs: storage-backends, metadata-backends, storage-reconciliation,
  app-secrets; update photos/share-links
- defaults reflected in specs: SQLite and local FS
2026-10-11 10:19:44 +03:00
iu cb272398ce feat(auth): store the token signing secret in the metadata layer
- app_settings table (PostgreSQL migration 000010, SQLite 000002)
- internal/settings repository (Get/Set/Ensure) wired into store.Repos
- secret generated with crypto/rand on first start and persisted; reused on restart
- drop required JWT_SECRET from env/config; app.New takes a TokenManager and
  storage.Open takes the resolved secret (URL_SECRET still overrides)
- docs updated
2026-10-11 10:18:31 +03:00
iu 176a8d8ced feat(metadata): SQLite backend, storage reconciliation and local defaults
- pluggable per-domain Repository interfaces; PostgreSQL and SQLite impls
- SQLite on modernc.org/sqlite (WAL, busy_timeout, foreign_keys, immediate tx)
- separate migrations/sqlite and db.MigrateSQLite; store.NewPostgres/NewSQLite
- cmd/reconcile compares object storage with the journal (report, then --delete)
- storage.Backend.List and photos.Repository.AllStorageKeys
- DB_BACKEND/SQLITE_PATH and STORAGE_BACKEND/FS_ROOT selectable; local defaults
  (sqlite ./galo.db, fs ./data)
- Makefile reconcile/reconcile-delete; docs and .env.example updated
2026-10-11 10:08:31 +03:00
iu ec8d907565 feat(storage): serve media through backend and add local FS backend
- introduce storage.Backend with S3 and local file-system implementations
- deliver all blobs via signed GET/HEAD /api/objects/<key> (Range, Content-Type)
- stream S3 objects through the backend; remove presigned URL path
- share shared HMAC Signer between backends
- remove S3_PUBLIC_URL; rename S3_PRESIGN_TTL to OBJECT_URL_TTL
- make storage label in health dynamic; nginx proxy_buffering off for objects
2026-10-11 09:15:03 +03:00
iu 7ae9bcf305 chore(openspec): archive media-object-layer and sync main specs
- move change media-object-layer to openspec/changes/archive/2026-10-10-media-object-layer
- add main spec specs/media-objects (opaque keys, object journal, shared
  objects, reference-based GC, lifecycle recovery, variant generation queue)
- update specs/photos deletion requirement to place photos in the trash
  without deleting storage objects
2026-10-10 23:32:21 +03:00
iu 96268ee16c feat(photos): separate object storage from metadata layer
Move physical file facts out of the photos table into an objects ledger:

- objects / photo_objects / variant_jobs tables (migration 000008)
- drop storage_key/thumb_key/preview_key/size/width/height/mime_type from
  photos (migration 000009); they are derived via joins to the original object
- album-free storage keys: objects/{id}/{role}.{ext}
- explicit GC via pending/ready/deleting states; objects deleted only when no
  photo references them
- background worker drives variant generation (durable variant_jobs) and GC
- copy/move are metadata-only; storage objects are shared between photos
- remove unused storage.DeletePrefix

Non-destructive to the external JSON API: size/dimensions/mime and URLs are
still returned, just sourced from the object layer.

OpenSpec change: media-object-layer
2026-10-10 23:31:41 +03:00