Skip to content

Latest commit

 

History

History
209 lines (172 loc) · 41.6 KB

File metadata and controls

209 lines (172 loc) · 41.6 KB

Changelog

All notable changes to Logia will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[1.1.0] - 2026-08-19

Added

  • Books API integration: Added two new providers for book media enrichment, following the existing dual-provider pattern (primary with key + secondary without key):
    • Google Books (key required — api_key_google_books): Primary provider for modern books. Rich metadata including title, authors, publisher, publish date, description, page count, categories, and multiple cover image resolutions. Free API key via Google Cloud Console (1000 req/day default quota). Rate-limited at 5 req/s.
    • Open Library (no key): Secondary/fallback provider from the Internet Archive. 20M+ titles with excellent coverage of older books (1800s–1970s). Covers fetched via covers.openlibrary.org. Rate-limited at 1 req/s (per their low-volume usage policy; the existing Logia/{version} User-Agent header satisfies their identification requirement).
    • Both providers map authors to credits (role: "Author"), page count to the duration field, and categories to genres.
  • Board games API integration (BoardGameGeek): Added BoardGameGeek XMLAPI2 as the first provider for the new board_game media type. BGG is the canonical board game database (100k+ titles) with designers, artists, publishers, mechanics, categories, player counts, and playing time.
    • Note on authentication: As of July 2nd, 2025, BGG requires application registration and a Bearer token (previously anonymous access was allowed). The token is configured via api_key_bgg in Settings → API Keys and obtained from https://boardgamegeek.com/applications. Sent as Authorization: Bearer {token} header on every request.
    • XML parsing: BGG returns XML (not JSON). Added quick-xml = "0.36" as a Rust dependency for streaming XML parsing of search results, thing details, and batch thumbnail lookups.
    • Search thumbnails: BGG's /search endpoint does not return thumbnails. A secondary batched /thing?id={id1},{id2},... call fetches thumbnails for the top 5 search results (1 additional grouped request).
    • Maps designers/artists/publishers to credits, categories+mechanics to genres, and playing time (minutes) to the duration field. Rate-limited at 1 req/s (BGG's rate limits are opaque and strict).
  • Cross-platform builds (Linux & macOS): Logia is now built and released for Linux and macOS in addition to Windows. The Release GitHub Actions workflow (.github/workflows/release.yml) now runs three parallel jobs on every v* tag push:
    • Linux (ubuntu-22.04): produces .deb, .AppImage, and .rpm packages. The AppImage is the recommended format for Linux users — it is the only Linux bundle that supports the in-app auto-updater (.deb/.rpm installs must be updated manually by downloading the new version).
    • macOS (macos-latest): produces a single universal .dmg that runs natively on both Intel and Apple Silicon (M1/M2/M3) Macs via --target universal-apple-darwin. The app is not code-signed with an Apple Developer certificate, so macOS Gatekeeper will show an "unidentified developer" warning on first launch — users can bypass it via right-click → Open (or xattr -dr com.apple.quarantine /Applications/Logia.app). The auto-updater works on macOS from this version onwards.
    • All three jobs upload their artifacts to the same GitHub Release, and latest.json (the updater manifest) now includes entries for windows-x86_64, linux-x86_64, and darwin-universal (or darwin-aarch64/darwin-x86_64).
  • Cross-platform DB instance lock: the single-instance database lock (which prevents two Logia processes from using the same profile DB simultaneously) was previously implemented with Windows-only kernel32 LockFile/UnlockFile calls and std::os::windows::io::AsRawHandle, preventing compilation on Unix. Refactored src-tauri/src/lib.rs to use conditional compilation: #[cfg(target_os = "windows")] keeps the existing LockFile/UnlockFile path, while a new #[cfg(unix)] path uses flock(2) (LOCK_EX | LOCK_NB for acquisition, LOCK_UN for release) via std::os::unix::io::AsRawFd. Behavior is identical on Windows; on Linux/macOS the lock is advisory (standard for Unix).
  • GitHub Pages landing page: added a static landing page at docs/index.html (served via GitHub Pages at https://cosmiir.github.io/Logia/) to present the app to new visitors. The page is a single self-contained HTML file (no external CSS/JS, no build step) styled after the Nebula theme (gradient background, #a855f7 accent, glass-morphism cards, cosmic glow backdrop). Includes a sticky top nav with anchor links, a hero section with the logo and CTA buttons (Download latest release → releases/latest, View on GitHub), the full feature list (8 glass cards), a responsive 2-column screenshot gallery, installation instructions (direct download + from source), tech stack table, license and Ko-fi support sections. Optimized for SEO: <html lang="en">, descriptive <title>/<meta name="description">, Open Graph and Twitter Card tags, <link rel="canonical">, robots: index,follow, theme-color, and JSON-LD SoftwareApplication structured data (with screenshot array). All screenshots were converted from PNG to WebP at quality 95 (preserving fine text) into docs/assets/, reducing total page weight from ~5 MB to ~1 MB. The logo (LOGIA.png) and favicon are also copied into docs/assets/ so the page is fully self-contained and indexable without depending on the main branch's raw file URLs. English-only for now (multi-language via separate /fr/index.html + hreflang is planned for later once the English page generates traffic).

[1.0.9] - 2026-08-17

Added

  • IGDB Video Games Integration: Replaced TheGamesDB with IGDB (Internet Game Database via Twitch Developer API) for video game media search and enrichment.
    • Supports Twitch OAuth2 client_credentials authentication with automatic in-memory token acquisition and caching.
    • Queries high-res cover artwork (t_cover_big), search thumbnails (t_thumb), 720p screenshots and artworks (t_720p).
    • Extracts exact game developer name via involved_companies (developer == true), genres, and estimated play duration (time_to_beat.normally in hours).
    • Updated Settings → API Keys with dual inputs for IGDB (Client ID) (api_key_igdb_client_id) and IGDB (Client Secret) (api_key_igdb_client_secret), linking directly to Twitch Developer Console (https://dev.twitch.tv/console).

Removed

  • TheGamesDB Provider: Removed TheGamesDB integration in favor of IGDB for improved video game metadata, cover art quality, search speed, and developer resolution.

Fixed

  • Production Build API Image Previews (CSP): Added https: and http: protocols to the img-src Content Security Policy directive in src-tauri/tauri.conf.json. Previously, external API image URLs (from TMDB, RAWG, IGDB, etc.) pre-filled during media creation/editing were blocked by the production webview's CSP prior to saving, displaying missing file icons in gallery preview slots until saved to local disk.
  • CDN Referrer Policy: Added <meta name="referrer" content="no-referrer" /> to index.html to ensure external CDN image hosts accept webview image requests without blocking custom Tauri scheme origins.

[1.0.8] - 2026-08-17

Added

  • Optional API enrichment: Logia is now offline-by-default with optional per-collection API enrichment. Users can map collections to external API providers to automatically pre-fill media details (title, creator, release date, status, synopsis, credits, and up to 8 gallery images) when creating or editing media entries. The feature is entirely optional — the app works fully offline without any API keys or provider configuration.
    • 9 API providers across 6 media types:
      • Films: TMDB (key required) + OMDb (key required)
      • Series: TMDB (key required) + TVMaze (no key)
      • Anime: Jikan/MyAnimeList (no key) + AniList (no key)
      • Manga: Jikan/MyAnimeList (no key) + AniList (no key)
      • Video Games: RAWG (key required) + TheGamesDB (key required)
      • Music: MusicBrainz (no key) + iTunes (no key)
    • Rust backend (src-tauri/src/api/): All API calls are handled server-side using reqwest with rustls — no CSP modifications needed, API keys never exposed to the frontend, and images can be downloaded directly to local storage. Includes a per-provider rate limiter (token bucket) with 429 retry + exponential backoff (1s, 2s, 4s), respecting each provider's individual rate limits (e.g. Jikan 3 req/s, MusicBrainz 1 req/s). MusicBrainz User-Agent is dynamically set to Logia/{CARGO_PKG_VERSION} per their API policy. Provider modules parse responses flexibly with serde_json::Value for graceful handling of missing fields.
    • 4 new Tauri commands: get_api_providers (list providers with key-availability status), search_api_media (parallel multi-provider search), get_api_media_detail (fetch full detail for a single result), download_api_image_to_media (download an image from a URL and save it to a media's gallery, reusing the existing WebP/EXIF/resize pipeline via the extracted save_image_bytes_to_media helper).
    • CollectionEdit: New "API Enrichment" section with a multi-select of providers grouped by media type (Films, Series, Anime, Manga, Video Games, Music). Providers requiring an API key are grayed out with a "Key required" badge until the key is configured in Settings. Providers with a configured key show a "Key set" badge.
    • Settings → API Keys: New tab for configuring API keys (TMDB, OMDb, RAWG, TheGamesDB) with save buttons and per-key status feedback. Includes the mandatory TMDB attribution notice required by their terms of service. An offline notice reminds users that enrichment is optional.
    • ApiSearchModal: Debounced search modal (500ms) triggered by a search icon next to the title field in MediaCreate/MediaEdit (only shown when the selected collection has providers enabled). Results display thumbnails (base64-encoded from Rust), title, year, creator, and a source badge per provider. Selecting a result fetches the full detail and pre-fills the form fields; up to 8 images are downloaded and added to the gallery automatically after the media is saved. Users retain full control to edit all pre-filled fields.
    • DB migration: Added api_providers TEXT column (JSON array of provider IDs) to the collections table. Migration is automatic on app launch.
    • i18n: Full English and French translations for all new UI strings (settings.api.*, collectionEdit.apiEnrichment.*, apiSearch.*, mediaCreate.searchViaApi).

Changed

  • README: Updated from "100% offline, no external API" to "offline by default, with optional API enrichment" to reflect the new feature while reassuring users that the app remains fully functional without any internet connection.
  • Image pipeline refactor: Extracted save_image_bytes_to_media from upload_media_image in commands/media.rs to share the core image processing logic (EXIF rotation, resize to max 1920px, WebP encode at quality 80) between the existing base64 upload path and the new API image download path. The function takes a &Connection and &Path directly instead of State<'_, AppState>, making it reusable from non-command contexts.

[1.0.7] - 2026-08-15

Fixed

  • Onboarding (CreateProfile) content clipping at high Windows display scaling: at 150% Windows "Mise à l'échelle" (and similar zoom levels), the onboarding flow clipped its content — the "Suivant" button was half-cut on step 3 (Profile), and the bottom of step 4 (Personalization, from the "Display density" card downward) was invisible and unreachable. Root cause: CreateProfile wrapped its content in a plain <div className="flex-1 flex flex-col p-8"> with no overflow-y-auto, while AppShell enforces overflow-hidden at the root — so any content exceeding the viewport height was silently clipped with no scrollbar. All other pages (Settings, Dashboard, Library, etc.) use the MainContent layout component, which provides overflow-y-auto, custom-scrollbar, and scrollbar-gutter: stable. Refactored CreateProfile to use MainContent like the rest of the app, bringing consistent scroll behavior, styled scrollbar, and stable layout (no width jump when the scrollbar appears/disappears). The decorative glow backdrop was switched from absolute inset-0 to fixed inset-0 so it stays centered in the viewport during scroll instead of drifting with the content.

Changed

  • Silent (in-app) updates: the updater now installs new versions silently on Windows, with no installer window shown to the user (behavior similar to Discord). Previously, the updater used the default passive install mode, which displayed a separate NSIS installer window with a progress bar during installation. Switched plugins.updater.windows.installMode to quiet in tauri.conf.json so the NSIS installer runs invisibly (/S /UPDATE): the user only sees the existing in-app progress bar in UpdateModal during download, then the app exits, the installer overwrites the binary in the background, and the app relaunches automatically on the new version. No UAC prompt is shown. Also made the NSIS bundle install mode explicit (bundle.windows.nsis.installMode = "currentUser", install in %LOCALAPPDATA%) as a guard against regressions — the quiet install mode cannot elevate admin privileges on its own, so a currentUser install is required. See RELEASE.md for details.

[1.0.6] - 2026-08-12

Added

  • Auto-update system: Logia now checks for updates automatically on launch via tauri-plugin-updater, using GitHub Releases as the update CDN (no dedicated server required). The updater fetches a signed latest.json manifest from https://github.com/Cosmiir/Logia/releases/latest/download/latest.json (served by the GitHub CDN, no rate limit, no authentication), verifies the bundle signature against a public key embedded in the app, and offers to download and install the new version in-place before relaunching. The check runs asynchronously 800ms after app mount so it never blocks startup. When an update is available, a modal dialog offers four choices: Update now (downloads, installs, and relaunches automatically with a progress bar), Download manually (opens the GitHub release page), Ignore (dismisses for this session, reproposed on next launch), and Don't remind me for this version (persists the skipped version number to localStorage, suppressing the modal until a newer version is released). A manual "Check for updates" button is also available in Settings → About, which forces a check (ignoring any skipped version) and displays the result in the same modal. Added tauri-plugin-updater and tauri-plugin-process (Rust + npm) dependencies, useUpdateCheck hook, UpdateModal component, i18n keys (update.*, settings.about.updates) in EN/FR, and a new Release GitHub Actions workflow (.github/workflows/release.yml) triggered on v* tags that builds the Windows NSIS installer + updater bundle, signs it with TAURI_SIGNING_PRIVATE_KEY, and uploads latest.json + installer + signature to the GitHub release via tauri-apps/tauri-action. See RELEASE.md for the release procedure and one-time signing key setup. Note: this is the first signed release — users on v1.0.5 or earlier must install v1.0.6 manually; all subsequent updates will be delivered automatically.

Fixed

  • MediaCreate attachment sort order on import: when importing new attachments and reordering them via drag-and-drop before saving, the sort order was lost on first save. Root cause: new attachments were uploaded using attachmentFilePathsRef (import order) instead of form.attachments (form order after drag-and-drop), and the reorderAttachments call only covered existing (already in DB) attachments — newly uploaded ones kept their sequential DB insert positions. Fixed by deriving upload paths from form.attachments in form order and including newly uploaded attachment IDs in the final reorderAttachments call.

  • MediaDetail attachments read-only: the attachments section in MediaDetail allowed renaming and reordering files (drag-and-drop handle, rename button, rename input). These actions should only be available in MediaCreate/MediaEdit. Removed the SortableAttachment component (drag handle + rename + confirm) and replaced it with a simpler AttachmentItem that only exposes the existing Read (CBZ/ZIP) and Download actions. Removed the associated DndContext/SortableContext wrappers, the localAttachments/renamingId/renameValue state, and the handleStartRename/handleConfirmRename/handleCancelRename/handleAttachmentDragEnd handlers. Cleaned up now-unused imports (@dnd-kit/*, GripVertical, Edit2, Check, useQueryClient, tauriApi).

  • Library horizontal scrollbar on scrollbar thumb drag: dragging the vertical scrollbar thumb in the Library caused a horizontal scrollbar (with a white-ish thumb) to appear at the bottom of the page. Root cause: handleContentMouseDown (drag-to-select rectangle handler) excluded clicks on cards and interactive elements but not clicks on the scrollbar itself. A scrollbar click fires on the <main> element at an X position beyond clientWidth, so the handler created a selectionBox div positioned outside the content bounds — extending scrollWidth and triggering a horizontal scrollbar (whose thumb appeared white due to .custom-scrollbar:hover). Fixed by adding an early-return guard in handleContentMouseDown that compares e.clientX/e.clientY against currentTarget.clientWidth/clientHeight (which exclude the scrollbar) and bails out when the click lands in the scrollbar gutter.

  • i18n (CustomDatePicker): the date picker was hardcoded in French (month/day names, placeholder "Sélectionner...", dd/mm/yyyy display and parsing, Monday-first calendar). Replaced with useTranslation() keys under a new datePicker namespace, formatDateFr() for locale-aware display (dd/mm/yyyy FR / mm/dd/yyyy EN), locale-aware parsing (dd/mm/yyyy FR / mm/dd/yyyy EN), and Sunday-first week for EN. Impacts MediaCreate, MediaEdit, Library filters, ObjectiveCreate and ObjectiveFormModal. Made formatDateFr() timezone-safe for yyyy-mm-dd inputs and added getCurrentLocale() / getFirstDayOfWeek() helpers.

Changed

  • Library detailed view (list rows): the progression bar used a hardcoded from-blue-500 to-purple-500 gradient that didn't match any theme accent. Replaced with a theme-aware progress bar that adapts automatically to all 5 themes via CSS variables (--theme-accent, --theme-accent-dark, --theme-accent-rgb): Nebula (violet), Midnight (blue), Ember (orange), Forest (green), Arctic (cyan). The fill now uses a subtle same-hue gradient (accent-dark → accent), a glossy top highlight (vertical white→transparent overlay on the upper half), a soft accent glow (box-shadow: 0 0 6px rgba(accent, 0.45)), and the track uses rgba(accent, 0.15) with an inset shadow for a recessed look. Pure CSS, no extra DOM nodes.

[1.0.5] - 2026-07-28

Fixed

  • Library grid oscillation: at certain window widths, the media card grid oscillated between N and N+1 columns (e.g. 6→7→6→7), causing cards to grow/shrink and jump between 1 and 2 rows in an infinite loop. Root cause: MainContent used useHasScrollbar to dynamically adjust right padding (pr-10pr-[28px], -12px) when the scrollbar appeared. This width change caused gridTemplateColumns: repeat(auto-fill, minmax(...)) to switch column count → height changed → scrollbar appeared/disappeared → width changed again → infinite loop. Fixed by replacing the useHasScrollbar + dynamic padding system with scrollbar-gutter: stable, which permanently reserves scrollbar space (6px) keeping clientWidth constant regardless of scrollbar visibility. Right padding set to pr-[34px] (40px - 6px gutter = 40px visual). Also removed the now-obsolete main.custom-scrollbar { margin-right: 3px } and main.no-scrollbar { margin-right: 0 } rules from global.css.
  • i18n (CollectionEdit): tracking display description used .replace() to inject an HTML <span> string into a translated string, which React rendered as literal text (visible <span ...> tags). Split trackingDisplay into trackingDisplayPrefix, trackingDisplaySeparator, and currentSuffix keys with proper JSX elements. Also fixed hardcoded French "actuel"currentSuffix key ("current" in EN).
  • i18n (CollectionEdit): hardcoded French "Affiché dans les objectifs :"objectivesDisplay key. Hardcoded fallbacks 'Consommer' and 'médias't('common.consume') and t('common.media') in both the objectives display and capacity hint.
  • i18n (ObjectiveCreate): hardcoded French title "Modifier l'objectif" / "Nouvel objectif" and subtitles → existing editObjective/newObjective keys + new editObjectiveSubtitle/newObjectiveSubtitle keys.
  • i18n (ObjectiveFormModal, ObjectiveCard): hardcoded French fallback 'Consommer't('common.consume').
  • i18n (GravityMarkdownEditor): hardcoded French strings in the media link/mention tool — toolbar button title 'Média' and hint 'Lier un média (Ctrl+M)', and footer keyboard hints naviguer, sélectionner, annuler — replaced with i18next.t() calls using new mediaMentionTitle, mediaMentionHint, navigateHint, selectHint, cancelHint keys.
  • i18n (MediaDetail): hardcoded French "Modifier" on the edit button → t('media.edit') (existing key). Hardcoded fallback 'Durée totale't('mediaDetail.duration') (existing key). Hardcoded "Passer" and "Mettre à jour" on the manga reader progress prompt → new mangaReader.skip and mangaReader.update i18n keys (EN/FR).
  • i18n (Stats): hardcoded French strings in PeriodSelector ("Toute la période", "Période perso.") and CollectionFilter ("Toutes les collections") → t('stats.allTime'), t('stats.customPeriod'), t('stats.allCollections') i18n keys (EN/FR).
  • i18n (Notifications): all notification titles and messages in notificationRules.ts were hardcoded in French (e.g. "Cette œuvre est en cours depuis plus de 30 jours sans progression."). Replaced all 7 notification rule strings + monthly report (including month names) with i18next.t() calls using new notifications.rules.* keys with interpolation params ({{title}}, {{progress}}, {{days}}, {{count}}, {{month}}, {{year}}, {{completed}}, {{abandoned}}, {{rating}}) in EN/FR. Also added missing notifications.none key and fixed hardcoded 'Dashboard' label in SharedHeader.tsxt('navigation.dashboard').
  • i18n (Notifications rendering): notification titles and messages were generated by the Rust backend with hardcoded French strings (e.g. "{} stagne", "Cette œuvre est en cours depuis plus de 30 jours sans progression.") and stored in the database, ignoring the app's active language. The frontend now renders notification titles/messages via i18n at display time using notification.type + notification.data (JSON), via a new getNotificationDisplay() helper in notificationConfig.ts. This helper maps snake_case notification types to camelCase i18n keys (stagnant_mediastagnantMedia, etc.) and passes interpolation params from the data field. Applied to both Notifications.tsx (full page) and SharedHeader.tsx (bell dropdown). Backend (notifications.rs) enriched: stagnant_media and waiting_media now compute actual days stagnant/waiting via SQL julianday() and include title + days in data; objective_deadline computes real days_until_end from end_date instead of hardcoded 7; objective_achieved includes count; monthly_report includes completed, abandoned, rating shortcuts. i18n messages updated: stagnantMedia.message and waitingMedia.message now use {{days}} instead of hardcoded "30 jours"/"90 jours".
  • i18n (MediaCreate, MediaDetail): getRatingCategory in ratingColors.ts returned hardcoded French strings ("Chef-d'œuvre", "Parfait", "Très bon", etc.) instead of using i18n. Replaced all 12 category labels with i18next.t('common.ratingCategory.*') calls, using the existing common.ratingCategory keys added in this release.
  • Dashboard stats: average rating, median, and standard deviation were all broken. Three root causes in get_dashboard_stats (Rust backend):
    • AVG(user_rating) included ratings of 0 (unrated media), inflating the average and making it inconsistent with rated_count which filtered > 0. Fixed by adding AND user_rating > 0 to the AVG subquery.
    • Ratings were read from the database as i32 via row.get::<_, i32>(0), but user_rating is stored as REAL (float). rusqlite silently failed the conversion, and filter_map(|r| r.ok()) discarded all rows — resulting in an empty ratings vector, hence median = 0 and std dev = 0. Fixed by reading as f64 then casting to i32.
    • An incorrect scale conversion (r / 10.0) * 100.0 multiplied already-0-100 ratings by 10 (e.g. 68 → 680). Removed since ratings are stored on a 0-100 scale per the CHECK constraint.
    • Frontend (UnifiedStatsCard): the average rating circle displayed a non-zero value even when rated_count was 0. Now forces rating100 = 0 when no media are rated.

Fixed

  • i18n (miscellaneous front-end): added missing common.ok / common.delete keys and fixed hardcoded UI strings in LanguagePicker (search placeholder, no-results, clear), Import (in-development step), Settings/ProfileSection (password/storage messages, window styles, avatar alt), Settings/PersonalizationSection (theme names, window control labels), Settings/DataSection (no other profile for merge), Library (numeric filter operators, min/value labels), MediaDetail (read more/collapse, lightbox hint), MangaReader (page number), TemplateManagement (new/edit/delete/default states), and crop modals alt text. Added matching EN/FR keys under common, languagePicker, themes, personalization.windowStyle, import, library.operators, mediaDetail, mangaReader.pageNumber, settings.profile, and templateManagement.

Added

  • Welcome tutorial: interactive coach-marks/spotlight tutorial that automatically starts after onboarding on first Dashboard visit. Guides new users through creating their first collection and first media item across 24 steps organized in 3 phases (Getting Started → Collection Setup → Media Creation). Steps cover: Dashboard welcome → Library navigation → collection bar hover discovery → "+" button → collection naming → dynamic fields intro → creator section → dates section → progression section → advanced section → visual column → save collection → Library "New" button → media collection dropdown → title → creator → status/date → synopsis → cover → genres → progress → review → rating → gallery → attachments → save media. Features include:
    • SVG mask spotlight with clean cutout, glassmorphism tooltip cards matching existing app style, pulse/glow animation on targeted elements
    • Phase-aware compact stepper indicator (shows current step within its phase, not just global position)
    • Action steps: interactive steps that require the user to click the highlighted element (e.g. navigate to Library, click "+", save collection, click "New") with an animated "Click to continue" banner; auto-advance on navigation to the target page
    • Info steps: non-blocking steps that highlight and explain a UI section; user clicks Next to proceed
    • Completion step: final step with "Don't show again" checkbox that persists tutorial_has_completed to backend
    • Progressive section locking: form sections (creator, dates, progression, advanced, visual on collection-edit; title, creator, status/date, synopsis, cover, genres, progress, review, rating, gallery, attachments on media-create) are locked until the tutorial reaches the corresponding step, preventing users from interacting with sections not yet explained
    • Portal/dropdown detection: the spotlight automatically includes open popovers, dropdowns, dialogs, and absolute/fixed children that extend beyond the target element; touching/overlapping rects are merged into a single exact rectilinear-union outline (L-shape supported) via grid decomposition polygon tracing, so no double edges or invented area appear
    • Tooltip collision avoidance: tooltip auto-repositions (flips to opposite side) if it overlaps the highlighted target, clamped to viewport boundaries
    • Header clamping: spotlight padding is trimmed at the header's bottom edge so the highlight border never bleeds into the header area
    • Click-blocking backdrop: clicks outside the active target area are blocked; the target itself remains interactive for action steps
    • Auto-scroll: target element is scrolled into view on each step change
    • Waiting state: spinner overlay shown while navigating between pages during multi-page steps
    • Keyboard shortcuts: Esc to skip, Enter to go next (on info steps), ArrowLeft to go back
    • Tutorial invitation badge: shimmer badge on the Dashboard for users who haven't seen the tutorial yet, offering to start it or dismiss it (persists tutorial_has_seen_invitation)
    • Per-profile persistence via backend settings sync (tutorial_has_seen_invitation, tutorial_has_completed)
    • Tutorial is optional and interruptible at any point (Skip button always visible)
  • Rating category labels (i18n): common.ratingCategory with 12 quality tiers (notRated, masterpiece, perfect, excellent, veryGood, good, fine, decent, average, passable, bad, terrible) in EN/FR
  • Media mention: mediaMentionTitle and mediaMentionHint (Ctrl+M) i18n keys for linking media in reviews, plus navigateHint, selectHint, cancelHint keyboard hint labels
  • Reader: skip and update buttons on the progress update prompt when finishing a CBZ/chapter
  • Attachment drop hint: attachmentDropHint i18n key replacing notesAndPoints, with drag-and-drop file browsing text (saves, configs, ebooks, archives...)
  • Stats: allYears, clickYearHint, clickMonthHint i18n keys for the Activity over time drill-down chart (EN/FR)

Changed

  • Stats page: replaced the "All Collections" dropdown filter with interactive click-to-toggle filtering directly on the Collection Breakdown donut chart. Clicking a collection segment or legend item toggles its selection (multi-select); all stats update to reflect only the selected collections. Empty selection = all collections shown. The donut always displays all collections (from period-filtered media) regardless of selection, with unselected segments dimmed. A "Reset" button appears in the section header when collections are selected. Removed the CollectionFilter dropdown component and the Filter icon import.
  • Stats page: replaced the flat monthly "Activity over time" bar chart with an interactive drill-down chart (year → month → day). Default view shows one bar per year; clicking a year drills into 12 monthly bars; clicking a month drills into daily bars. Breadcrumb navigation at the top allows clicking "All Years" or the year name to navigate back up. Clicking a year/month also sets the global period filter for the entire page, so all other charts/stats update to reflect the selected time range. The chart uses media filtered by collection + status (but not period) so it always shows all available years.
  • Stats page: made the Status Breakdown donut chart interactive with click-to-toggle filtering, matching the Collection Breakdown pattern. Clicking a status segment (e.g. "Completed", "Not Started") filters all other charts on the page to only show media with that status. Multiple statuses can be selected. A "Reset" button appears in the section header when statuses are selected. The donut always displays all statuses (from period + collection filtered media) regardless of selection, with unselected segments dimmed.
  • Stats page: removed the global PeriodSelector dropdown from the toolbar. Period filtering is now handled exclusively by the Activity over time drill-down chart. Removed the PeriodSelector component, getAvailableYears helper, and ChevronDown icon import as they are no longer used.
  • Stats page: moved the "Activity over time" section above the Status/Collection breakdown sections for better visual hierarchy.
  • Stats page: updated the filter pipeline to support three independent filters (period + collections + statuses) with intermediate filtered media levels so each donut chart respects the other's filter but not its own, preventing circular filtering.
  • Stats page: redesigned the Top Rated / Worst Rated media lists (MediaRankList) from simple text rows to compact inline cards. Each item now displays a cover image (40×80px) with a rating badge overlay, the media title, creator pills (matching MediaCard style with +N overflow), and collection + progress status badges stacked in a right column. All items have a fixed height for visual consistency. Each card is clickable and navigates to the corresponding media detail page.

[1.0.4] - 2026-07-25

Fixed

  • MediaCard flip (back face) normal mode: multi-line titles were clipped — the second line showed only its top half due to flex compression. Added shrink-0 to the title and header to preserve their natural height.
  • Tooltip (onlyWhenTruncated): truncation detection now also checks vertical overflow (scrollHeight > clientHeight), enabling tooltips on line-clamp elements that are truncated vertically.
  • Escape key in MediaDetail: when a viewer (MangaReader CBZ or gallery Lightbox) was open, pressing Escape closed the viewer AND triggered a navigation back. Now Escape only closes the viewer; a second Escape is needed to go back. Fixed by adding data-escape-to-close attribute to viewer containers and updating the global shortcut handler to check for it before calling goBack(). Also moved the Lightbox keyboard listener from document to window to ensure correct event ordering.
  • MediaCard hover: after deleting a media in the library, the first hover on any card caused the title to jump up (info bar expanded to 140px for one frame before the measured height was applied). Fixed by changing the CSS fallback for --card-info-h-hover from 140px to var(--card-info-h, 64px), so the default hover height matches the non-hover height until handleMouseEnter sets the correct measured value.
  • Export page (ZIP mode): the selection tick on export level cards (DB only / DB + images / Full backup) slid between cards when switching mode due to layoutId animation. Replaced with a simple fade + scale transition (0.15s) so the tick appears and disappears in place instead of sliding.
  • Export page (format selection): same layoutId sliding issue on the format cards (CSV / TSV / Markdown). Replaced with the same fade + scale transition.
  • Export page (collection filter): same layoutId sliding issue on the collection scope cards (All / Specific). Replaced with the same fade + scale transition.
  • Export page: large empty gap between the stepper and content during step transitions. Caused by AnimatePresence mode="sync" leaving both old and new content in the DOM simultaneously. Changed to mode="wait" and reduced stepper bottom margin from mb-8 to mb-4.
  • Image upload: files uploaded during media editing could silently overwrite existing images on disk. Filenames were generated using the upload position (e.g. cover_100_full.webp), and since startPosition = 100 is reused on every edit, uploading a file with the same name as an existing image overwrote it. Fixed by replacing the position in the filename with a timestamp + random suffix ({stem}_{YYMMDD-HHMMSS}_{random}_full.webp).

Changed

  • MediaCard flip (back face) normal mode: genres now limited to 2 visible pills (was 3) with max-w-[60px] truncation and flex-nowrap to prevent multi-line wrapping that pushed the attachments count out of view. Tooltip on each genre pill shows the full name on hover. The +N badge also has a tooltip listing remaining genres.
  • Export ZIP: switched compression method from Deflated to Stored for all files. Media files (WebP, JPEG, PDF) are already compressed — recompressing them wasted CPU for negligible size gain. The ZIP is now written significantly faster on large backups (e.g. 7+ GB full backup).
  • Export ZIP: files are now streamed to the ZIP in 64 KB chunks via BufReader instead of being loaded entirely into memory with std::fs::read(), reducing RAM usage on large exports.

Added

  • Export ZIP: real-time progress bar during archive creation. Shows percentage, current file name (left-aligned, fixed), file counter X/Y (right-aligned), and bytes processed/total. Uses Tauri's Channel IPC for live progress events from the Rust backend.
  • MediaCard flip (back face): tooltip on the title when truncated, showing the full title on hover in all density modes (compact, normal, large)
  • Library: "Flip all cards" toggle button in the toolbar (between sort and view mode buttons) — when activated, all media cards flip to their back face simultaneously, loading details asynchronously for each card
  • Attachment rename: inline rename of CBZ/files attached to a media directly from MediaDetail (pencil icon → edit name → Enter/blur to confirm, Escape to cancel). Renames both the database record and the physical file on disk.
  • Attachment drag & drop reorder in MediaCreate: attachments can be reordered via drag & drop (using dnd-kit) during media creation/editing. Order is persisted on save via reorder_media_attachments.
  • Attachment drag & drop reorder in MediaDetail: attachments can be reordered inline with immediate persistence. Uses optimistic updates with rollback on error.
  • Attachment inline rename in MediaCreate: existing attachments can be renamed inline during media editing, with changes saved on submit.
  • Database migration: added position column to media_attachments table with auto-migration for existing databases. Attachments are now ordered by position instead of created_at.
  • New Tauri commands: rename_media_attachment (renames file on disk + updates DB) and reorder_media_attachments (batch updates positions in a transaction).

[1.0.3] - 2026-07-22

Fixed

  • Database corruption prevention: added PRAGMA wal_checkpoint(TRUNCATE) on app exit via RunEvent::ExitRequested handler, ensuring WAL data is properly merged into the main database file before shutdown
  • create_media and update_media: wrapped all 3 operations (media insert/update + genre linking + credit linking) in a single atomic transaction instead of separate unchecked transactions, preventing partial writes if one step fails
  • switch_profile: lock file is now acquired for the new profile BEFORE updating the manifest, preventing an inconsistent state where the manifest says profile B is active but the connection is still on profile A
  • switch_profile: WAL checkpoint on the old connection before swapping to the new profile's database

Added

  • Lock file (logia.db.lock) using Windows LockFile API to prevent two instances of Logia from using the same profile database simultaneously. Lock is acquired at startup and on profile switch, with RAII cleanup on exit.
  • GFS (Grandfather-Father-Son) automatic database backup: creates a daily copy of logia.db in backups/ on app startup and on profile switch. Rotation keeps 7 daily, 4 weekly, and 12 monthly backups, deleting older ones automatically.

Changed

  • genres::link_to_media and people::link_to_media: removed inner unchecked_transaction since callers (create_media/update_media) now wrap operations in a global transaction
  • Backup rotation: weekly grouping now uses (year, week) instead of week alone to avoid cross-year collisions
  • Backup rotation: monthly retention now uses calendar date comparison instead of age_days <= 365 for precise 12-month retention

[1.0.2] - 2026-07-22

Fixed

  • MediaCard flip (back face) compact mode: creators were hidden, now visible with icon-based compact layout (1 creator + +N count)
  • MediaCard flip compact mode: genres now hidden to free space for creator display
  • MediaCard flip compact mode: creator +N count merged as superscript next to the pill instead of a separate truncated element
  • MediaCard flip all modes: creator pills with long names (e.g. "Everything Unlimited Ltd.") now truncate with ellipsis and show full name in a tooltip on hover
  • Library: creator filter popup showed an empty list because useDistinctCreators was only enabled when the filter presets menu was open (isFilterOpen), not when a creator filter pill popup was opened. Now also enabled when an active creator filter exists.

[1.0.1] - 2026-07-20

Fixed

  • CSV import: fixed incorrect column name progressCurrentprogress_current in the INSERT query, which caused "table media has no column named progressCurrent" errors on every imported row
  • Images not loading in production build: fixed CSP img-src directive from https://asset.localhost to http://asset.localhost to match Tauri v2's asset protocol scheme on Windows
  • Media sort by "recently added" broken after CSV import: imported media used RFC 3339 timestamps (2026-07-20T17:47:00+00:00) while media created normally used SQLite format (2026-07-20 17:47:00). Since SQLite sorts DATETIME lexicographically, T (ASCII 84) > space (ASCII 32), causing all imported media to sort before manually created media regardless of actual date. Fixed by using SQLite-compatible timestamp format in CSV/profile imports.
  • CSP: added blob: to img-src directive to allow blob URL images to load

Changed

  • Fonts: replaced Google Fonts CDN (fonts.googleapis.com) with local @fontsource/inter package to eliminate external network dependency and improve offline reliability