Commit Graph
112 Commits
Author SHA1 Message Date
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 d5d3f55db8 chore: ignore python bytecode 2026-10-11 20:41:20 +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 6ccb7764e8 chore: refresh generated OpenSpec commands and skills 2026-10-11 18:10:34 +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 a5d0cee0eb feat(ui): link album rail title to overview 2026-10-11 13:27:39 +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 29c2594a9b Merge pull request 'feat: local-first storage and metadata backends' (#3) from feature/local-fs-storage-backend into develop
Reviewed-on: #3
2026-10-11 07:44:15 +00:00
iu e0b4db926f feat(server): log metadata and storage backends at startup 2026-10-11 10:34:30 +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
iu 9effc0fc82 Merge pull request 'feat: Lightroom-style workspace UI' (#2) from feature/lightroom-workspace-ui into develop
Reviewed-on: #2
2026-10-10 19:56:14 +00:00
iu 384f126d8a chore(openspec): archive lightroom-workspace-ui and sync main specs
- synced delta specs into main specs: albums (+3 reqs, MODIFIED deleting album -> photos to trash), auth (+1), photos (+8), and created new ui-workspace spec (29 reqs)
- moved the change to openspec/changes/archive/2026-10-10-lightroom-workspace-ui/
2026-10-10 22:47:33 +03:00
iu 61b45b2742 feat: context menus, unified photo panel, tags, album trash and counters
- backend: deleting an album moves its photos to the trash without losing files (photos.album_id nullable, FK ON DELETE SET NULL, migration 000006; dropped S3 prefix delete); restore accepts an album target (album_required when the album is gone); user-scoped tags (000007) with GET /tags, POST/DELETE /photos/:id/tags, tags included in photo responses; GET /trash/count
- frontend: right-click context menus for photos (grid + viewer) and albums (rail + overview); single shared PhotoPanel reused in viewer and gallery selection (not a copy); viewer download/delete moved to the panel 'Действия'; transparent compact viewer top bar; trash entry as last rail item with per-album photo counts and trash count; album rename/delete dialogs
- openspec: sync lightroom-workspace-ui artifacts (specs photos/ui-workspace, design #24-30, tasks 9.17-9.24)
2026-10-10 22:45:41 +03:00
iu 2d6a3fea1a feat: photo multiselect, move/copy, trash and compact chrome
- backend: soft delete + trash (photos.deleted_at, migration 000005), GET /trash, restore, permanent delete with storage-key refcount (S3 objects removed only when no other album references them); batch endpoints photo-batch/{delete,move,copy} - metadata only, copies clone rows sharing storage keys
- frontend: Ctrl/Shift multiselect; right-panel 'Действия' as icon text rows (Открыть (просмотр), Переместить, Копировать, Удалить); drag selected photos onto rail album -> TransferDialog; /trash route; trash entry as last item in the left rail; empty right panel hidden on overview
- frontend: compact top bars (app header and viewer), compact theme switcher
- openspec: sync lightroom-workspace-ui artifacts (photos/ui-workspace specs, design #20-24, tasks 9.11-9.17)
2026-10-10 22:13:17 +03:00
iu 78996bc053 feat(frontend): select photo in album grid and show info in panel
- single click selects a photo (ring + aria-pressed) and shows its info in the right panel without navigating; double click opens the viewer
- shared 'selection' store; panel uses the route photo in viewer mode and the selected photo in album mode; selection resets on album change
- openspec: sync lightroom-workspace-ui (grid-selection requirement, viewer chrome wording without timers, nested f fullscreen, tasks 9.9/9.10)
2026-10-10 21:33:59 +03:00
iu 642e3bcf8a fix(frontend): immediate viewer chrome and correct f fullscreen
- remove all hide timers in Viewer; top/bottom bars and arrows now show/hide immediately
- arrows reveal only within the viewer bounds (no longer stay visible when the pointer is over the left/right panels)
- f (and the viewer button) toggle the viewer's own fullscreen via nested fullscreen, so f works inside app-level fullscreen without turning it off
2026-10-10 21:26:46 +03:00
iu 1b4906648d feat(preferences): configurable photo info attributes stored in user profile
- backend: users.preferences jsonb (migration 000004), Me returns preferences, PATCH /auth/me/preferences with validation (visible list + labels)
- frontend: gear in info section opens PhotoInfoSettingsDialog; base attribute set shown by default, remaining under ellipsis; per-attribute visibility and custom labels persisted to the profile; reset clears settings from the profile
- frontend: dialog has select-all/deselect-all icon buttons (no 'show all'); EXIF constants extracted to lib/exif.ts
- fix: render modal overlays via portal to document.body so they center in the viewport despite transformed panel/rail ancestors
- openspec: sync lightroom-workspace-ui artifacts (spec/design/proposal/tasks 9.8)
2026-10-10 21:04:08 +03:00
iu e1592549b2 feat(photos): full EXIF metadata extraction, backfill and info panel
- backend: switch EXIF parser to dsoprea/go-exif/v3 (all recognised tags, numeric normalization); store in photos.exif jsonb (migration 000003); add storage.Get and cmd/backfill-exif to backfill existing photos from S3 originals
- frontend: info panel renders all EXIF attributes with RU labels, enum translation and units (1/N, f/N, mm, EV); service tags hidden
- viewer: remove make-cover action (cover managed from panel only), icon-only download
- openspec: sync lightroom-workspace-ui artifacts (EXIF, cover-from-panel) and add plan for configurable info attributes/labels stored in user profile
2026-10-10 20:31:34 +03:00
iu d8da885020 feat(frontend): viewer zoom loads original and Space resets to fit
- in zoom, preload the full-resolution original and swap it in once loaded
  (preview is shown until then); revert to preview on zoom exit
- Space returns to the fitted view from any zoom level; from fit it goes to 100%
- derive zoom scale from the photo's true dimensions so 100% is accurate
2026-10-10 19:55:10 +03:00
iu dbf116999d chore(openspec): sync lightroom-workspace-ui with implemented changes
Reflect post-planning work in proposal, design, specs (albums/photos/
ui-workspace) and tasks: photo rename, viewer delete/zoom/fullscreen,
resizable and collapsible panels with EXIF, upload size limit, album
counts, themes/scrollbars, back navigation.
2026-10-10 19:45:55 +03:00
iu d1e3aa85b4 feat(frontend): viewer fullscreen chrome and pointer-accurate zoom
- fullscreen: auto-hide top toolbar and filmstrip, revealed at top/bottom
  screen edge and hidden 1s after the cursor leaves
- arrows < >: edge-reveal in both normal and fullscreen; hide 300ms after
  leaving; hidden while zoomed
- zoom: wheel zooms only (no scroll, overscroll contained), anchored to the
  pointer, capped at 200%; Space toggles 100%/fit; pan via drag/scroll
2026-10-10 19:45:55 +03:00
iu b968248e3d style(frontend): theme-aware scrollbars
Thin scrollbars; thumb uses --border (hover --text-subtle), transparent
track. WebKit + Firefox, follows the active theme.
2026-10-10 19:45:55 +03:00