Commit graph

410 commits

Author SHA1 Message Date
KhooLy
da8ef1dfb5 fix: reliably restart hero slide indicator fill animation
The dot indicator relied on a React key remount to restart the CSS
heroIndicatorFill keyframe on each slide change, which WebKitGTK
sometimes fails to pick up as a fresh animation start, leaving the
active dot frozen at 0% while the underlying slide timer kept
advancing normally. Drive the restart imperatively via a ref with a
forced reflow instead.
2026-07-30 19:58:32 +03:00
KhooLy
674921e250 refactor: split CalendarScreen.tsx into calendar/ parts
Extract the header, grid, day dialog, and artwork-fetch hook from
CalendarScreen.tsx into src/screens/calendar/, along with their shared
styles and utils.
2026-07-30 14:17:32 +03:00
KhooLy
d2c30735a8 refactor: split ReactPlayerOverlay.tsx into player/ components and hooks
ReactPlayerOverlay had grown to cover the whole in-player UI plus a
long tail of behavior hooks (gestures, telemetry, casting, media
session, Anime4K, keyboard shortcuts, track/subtitle controls, window
mode, etc). Break the JSX into PlayerBottomControls, PlayerFeedback,
PlayerMiniMode, PlayerOverlayDecorations/Styles, PlayerSkipPrompt,
PlayerStatsOverlay, PlayerStreamLinksMenu, PlayerSupplementalPanels,
PlayerTrackPanel, and SoftwareVideoCanvas, and move each behavior
concern into its own usePlayer*.ts hook.
2026-07-30 14:17:20 +03:00
KhooLy
6c6c484bb6 fix: keep seek preview thumbnail box stable and drop buffered stat row
The thumbnail container was only rendered once an image loaded, causing
the preview to jump size; keep the box mounted and swap the image in.
Also drop the redundant "Buffered" row from the torrent stats popover
and show downloaded progress with one decimal place.
2026-07-30 14:17:10 +03:00
KhooLy
6d29d033e0 refactor: split usePlayer.ts into focused hooks
Pull the embedded-mpv lifecycle, scrobbling, progress persistence,
retry, playback-start, and close-action logic out of usePlayer into
their own hooks, mirroring the existing usePlayerNativeEvents split.
2026-07-30 14:17:01 +03:00
KhooLy
f39ed72463 refactor: move playback/plugin/tmdb decision logic into fluxa-core
Replace duplicated JS logic for clearing playback progress, merging
continue-watching lists, selecting plugin scrapers, and resolving TMDB
ids/urls with coreInvoke calls into fluxa-core, so the frontend just
executes plans the core computes. Drops tmdbShared.ts now that its
helpers live in fluxa-core.
2026-07-30 14:16:52 +03:00
KhooLy
5956f5adec refactor: split engine.ts into runtime, core-client, content, and library modules
engine.ts had grown into a mixed bag of engine lifecycle calls, generic
core-invoke plumbing, and content/library-specific core wrappers.
Split those concerns out and re-export them from engine.ts as a barrel
so existing imports keep working.
2026-07-30 14:16:36 +03:00
KhooLy
19ffe6bff6 refactor: unify HeroSection and ModernDetailLayout trailer hooks
useHeroTrailer.ts and useModernDetailTrailer.ts were ~90% identical
(resolve/playback/subtitle-sync/stall-timeout/mute/fullscreen logic for a
YouTube trailer). Merged into a single useTrailerPlayback hook taking a
list of candidate trailer ids, an isActive gate, and returning the same
shape both call sites already used. HeroSection now tries all of a meta's
trailers in order (previously only tried the first one), matching what
ModernDetailLayout already did — a strict improvement, not a behavior
change either side relied on.
2026-07-30 03:15:04 +03:00
KhooLy
4557e49579 refactor: deepen ModernDetailLayout.tsx split into hero/action/meta/season parts
ModernDetailLayout.tsx dropped from 518 to 369 lines. The background+trailer
hero moves to ModernDetailHero.tsx, the play/watchlist/completed/dropped
button row moves to ModernDetailActionRow.tsx, the ratings/genre/description
block moves to ModernDetailMetaBlock.tsx, the season dropdown + prev-season
dialog moves to ModernDetailSeasonSection.tsx, and the episodes-tab body
joins DetailsTabContent/RelatedTabContent in ModernDetailTabs.tsx as
EpisodesTabContent.
2026-07-30 03:11:16 +03:00
KhooLy
87c54c3b13 refactor: deepen App.tsx split with screens module, gate screens, and two hooks
App.tsx dropped from 708 to 544 lines. Sentry-wrapped/lazy screen imports
move to appScreens.tsx, the welcome and profile-selection gate screens
move to AppGateScreens.tsx, detail-navigation state/handlers move to
useDetailNavigation (with a guardedPlayRef indirection to break the
ordering dependency on usePlayer), and nav-layout/UI-scale prefs move to
useAppLayoutPrefs.
2026-07-30 03:11:02 +03:00
KhooLy
4c478511d8 refactor: split LibraryScreen.tsx into styles, parts, and two hooks
LibraryScreen.tsx dropped from 737 to 395 lines. Styles move to
libraryScreenStyles.ts, CircleBtn/TabChip/HistoryTimeline move to
LibraryScreenParts.tsx, bulk-selection state and actions move to
useLibraryBulkSelection, and collections/folder management moves to
useLibraryCollections.
2026-07-30 03:02:57 +03:00
KhooLy
f0336da443 refactor: split SearchScreen.tsx into styles and presentational parts
SearchScreen.tsx dropped from 770 to 223 lines. Style objects move to
searchStyles.ts, and the row/chip/card subcomponents move to
SearchScreenParts.tsx. Dropped SearchResultRow, an unused dead component
with no callers in the original file.
2026-07-30 02:59:15 +03:00
KhooLy
55a1183e6e refactor: split SettingsUI.tsx into icons, options, layout, controls, and rows
SettingsUI.tsx dropped from 782 to a 46-line barrel re-exporting from
settingsIcons.tsx, settingsOptions.ts, SettingsLayout.tsx,
SettingsControls.tsx, and SettingsRows.tsx. Kept as a barrel since 9
other files import from './SettingsUI' directly.
2026-07-30 02:56:30 +03:00
KhooLy
7dea78c71e refactor: split ModernDetailLayout.tsx into parts, tabs, and a trailer hook
ModernDetailLayout.tsx dropped from 844 to 518 lines. GenreTag/SimilarSourcePicker
move to ModernDetailParts.tsx, the duplicated details/related tab JSX (previously
repeated for series and movies) is deduplicated into ModernDetailTabs.tsx, and the
hero trailer resolve/playback/subtitle state moves to useModernDetailTrailer.
2026-07-30 02:52:54 +03:00
KhooLy
826d62d550 refactor: split App.tsx into constants module and two event hooks
App.tsx dropped from 921 to 708 lines. Static styles/constants move to
appConstants.ts, native-player Tauri event listeners move to
useNativePlayerEvents, and global keyboard shortcuts plus window
fullscreen tracking move to useGlobalShortcuts.
2026-07-30 02:49:31 +03:00
KhooLy
7ef4242151 refactor: split HeroSection.tsx into styles, small parts, and a trailer hook
HeroSection.tsx dropped from 997 to 345 lines. Style objects move to
heroStyles.ts, NavArrow/HeroIconBtn/helpers move to HeroSectionParts.tsx,
and all trailer resolve/playback/subtitle state moves to useHeroTrailer.
2026-07-30 02:44:36 +03:00
KhooLy
8caab8264a refactor: split AccountSection.tsx into presentation, hook, and detail-view modules
AccountSection.tsx dropped from 982 to 338 lines. Provider connect/disconnect/sync
state and OAuth handlers move to useIntegrationAccounts, the selected-integration
detail screen moves to AccountIntegrationDetail, and icon/PKCE/form presentational
pieces stay in accountPresentation (already extracted in a prior pass).
2026-07-30 02:39:44 +03:00
KhooLy
5e64b20ea9 refactor: derive import category/dry-run gating from fluxa-core
Trakt/Simkl/Stremio now call the new importApplyPlan core method
instead of each hand-writing its own wants()/dryRun guards around
mergeExternalWatchlist/mergeExternalWatched; AniList and Nuvio pass
categories/dryRun straight into their existing combined-plan core
calls instead of gating the result afterward by hand. No behavior
change — same merges, same counts, same writes fire as before, now
sourced from one shared decision function per provider shape instead
of duplicated per-file TS conditionals.
2026-07-29 19:58:46 +03:00
KhooLy
d141f3a378 refactor: derive cross-provider push plan from fluxa-core
crossProviderPush.ts now calls the new pushPlan core method instead
of hand-branching per destination provider/category itself. The
provider/category capability matrix (which push shape applies to
which destination) now lives once in fluxa-core, reusable by any
future host platform through the same core_invoke("pushPlan", ...)
call — no behavior change on desktop, same push calls fire as before.
2026-07-29 19:45:13 +03:00
KhooLy
e0d9ad31b5 feat: show chosen destination with logo in Import preview
Reminds the user, below the scanned counts, exactly where the import
will push to (or that it's local-only) before they hit Continue.
2026-07-29 19:06:15 +03:00
KhooLy
b357335a06 feat: show source provider logo next to Import dialog title 2026-07-29 19:04:40 +03:00
KhooLy
0cd1f253bf feat: proper dropdown with provider logos for import destination
Replace the always-visible button list with a real trigger+popover
dropdown (reusing the existing Popover/FilterDropdown pattern used
elsewhere in the app), and show each provider's logo next to its name
in both the trigger and the menu.
2026-07-29 19:03:38 +03:00
KhooLy
1b7ae02d97 fix: replace native select with themed buttons in ImportDialog
The native <select> rendered with an unstyleable white background in
the Tauri webview, making the destination text unreadable. Replaced
with a custom button list matching the dialog's dark theme. Also
reworded the label from "Also push to" to "Push to" — it's the only
place data gets pushed, not an addition to something else.
2026-07-29 18:59:58 +03:00
KhooLy
b0a6ec3161 feat: scan-then-confirm flow for Import, showing real counts first
Every provider sync function now supports a dryRun flag that fetches
exactly as before (respecting the categories filter) but skips every
local write, delta-cache update, and provider-library save. The
Import dialog uses this to add a Scan step: pick categories and an
optional destination, hit Scan to see real per-category counts from
the source, then Continue to actually commit the import (and
destination push) — or Back to change the selection with nothing
having been written yet.
2026-07-29 18:52:25 +03:00
KhooLy
fd58e675cc feat: push imported categories to another connected provider
The Import dialog now has a destination dropdown (other connected
services, excluding the source) below the category checkboxes.
Default stays local-only; picking a destination pushes the selected
categories' resulting local state to that provider's real account
after the pull, reusing each provider's existing push primitives
(watchlist add, mark-watched, and for Continue Watching, Trakt/Simkl's
scrobble/pause endpoint — newly exported from scrobble.ts — which
already accepts an arbitrary historical position, not just live
playback).
2026-07-29 18:41:49 +03:00
KhooLy
7b4b3b20c4 feat: add selective category import for sync providers
Each provider's detail page (Trakt/Simkl/AniList/Nuvio/Stremio) now has
an Import button alongside Sync Now, opening a checkbox dialog so the
user can choose exactly which categories (watchlist, continue
watching, watched history, plus Nuvio's collections/addons and
Stremio's addons) get pulled into the local library. Sync Now keeps
pulling everything, since omitting categories still means "all" in
every provider's sync function — this also preserves useAppInit's
automatic full sync on startup.
2026-07-29 18:15:02 +03:00
KhooLy
f3acedc988 feat: confirm before irreversible library/account/download actions
Reuse the existing ConfirmDialog (profile deletion pattern) for bulk
library removal, dropping a Continue Watching item, disconnecting a
sync account, and deleting a downloaded file — all previously fired
immediately with no way back.
2026-07-29 17:57:22 +03:00
KhooLy
111964c241 Cap desktop trailer playback at 1080p 2026-07-29 17:55:37 +03:00
KhooLy
a26db79594 fix: route source-error message through i18n 2026-07-29 16:34:48 +03:00
KhooLy
1d933cdf65 feat: proxy stream requests locally to surface real upstream errors
Route playback URLs through a local 127.0.0.1 proxy so failed streams
report the actual upstream HTTP status/body instead of a generic mpv error.
2026-07-29 16:33:04 +03:00
KhooLy
6d87c5fbfc refactor: remove legacy detail screen view, always use modern layout
Drops the detailEpisodeViewMode setting and its ChoiceTile toggle so
DetailScreen always renders ModernDetailLayout.
2026-07-29 16:22:38 +03:00
KhooLy
d2371f600d refactor: rename coreSimklScrobbleAction to coreScrobbleCloseAction
Matches the fluxa-core rename — this threshold isn't simkl-specific,
it's shared by both Trakt and SIMKL scrobbling.
2026-07-29 16:22:38 +03:00
KhooLy
1552379ac9 refactor: delegate scrobble threshold, subtitle matching/dedup to fluxa-core
Trakt/SIMKL scrobble-close threshold, torrent sibling-subtitle matching,
and subtitle language dedup were duplicated logic living in the desktop
shell; route them through fluxa_core so mobile doesn't have to reimplement
the same policy.
2026-07-29 16:22:38 +03:00
KhooLy
c03b3fb389 refactor: route Trakt/SIMKL sync-delta decisions through fluxa-core
Move the activity-diffing and SIMKL delta-merge logic behind new core
calls (traktActivityDiff, simklResourceSyncPlan, simklMergeDelta) instead
of duplicating that decision logic in TS, so a second platform can reuse
it without reimplementing it. The desktop layer now only owns the actual
fetch/retry/storage I/O.
2026-07-29 16:22:38 +03:00
KhooLy
fe26bd0bef perf: skip unchanged Trakt sync resources using last_activities timestamps
Trakt was doing a full playback/watchlist/watched pull on every sync,
unlike SIMKL which already skips unchanged resources via its activities
endpoint. This adds the same pattern using Trakt's /sync/last_activities,
reusing the last cached result per resource when its timestamp hasn't
moved since the last sync.
2026-07-29 16:22:38 +03:00
KhooLy
56b9c7c8c7 feat: show meta detail from the addon the catalog item came from
Stamps catalog items (home rows, discover, search, folder paging)
with the addon transportUrl/content type they were fetched from, and
threads that through the detailLoadRequested dispatch. fluxa-core's
resource_fetch_plan_json now pins the metaDetail fetch to that addon
when known, instead of always racing every meta-capable addon by
priority order and discarding which catalog the user actually opened
the item from.
2026-07-29 16:22:38 +03:00
KhooLy
73c2d4cade fix: increase cast character-name contrast, matching the actor-name treatment 2026-07-29 16:22:38 +03:00
KhooLy
5530f9f11e fix: pick meta/season-episode race winner by addon priority, not network speed
metaDetail and seasonEpisodes requests race every meta-capable addon
in parallel and use Promise.any, which resolves as soon as ANY addon
answers with non-empty data — meaning a fast but sparse addon (e.g. a
torrent-indexing addon that only echoes back a bare-bones meta object)
could win over a slower but far richer one (a metadata addon with full
cast lists and TMDB photo URLs already baked in), purely because it
happened to respond first. Verified directly against a real addon
(AIOMetadata) that Cats (2019) has 10 cast members with photos in
app_extras.cast — data the app was discarding whenever some other
configured addon's race response landed first.

Switched to Promise.allSettled + picking the first fulfilled result in
the user's configured addon order (their priority), so meta selection
is now deterministic and respects addon order instead of arrival
jitter. Trades a small amount of best-case latency (bounded by each
addon's own request timeout, not by whichever is fastest) for
predictable, richer results.
2026-07-29 16:22:38 +03:00
KhooLy
ad58f59bf1 fix: stop cast/crew and trailer-candidate effects from re-firing on unrelated state updates
Root cause behind both the flaky cast/crew list and the duplicate
YouTube subtitle fetches (429s): every detail-state update — ratings,
trailers, fanart, whatever — comes from a fresh JSON.parse of the
whole state tree, so nested arrays/objects (displayMeta, displayTrailers)
get new references even when their actual content didn't change. Any
effect keyed on those references re-fires needlessly.

- Cast/crew effect: skip the async rebuild when a content signature
  (title id + cast + links) hasn't actually changed since last run.
- trailerVideoIds: return the previous array reference when its
  contents are unchanged, so the hero-autoplay effect (and everything
  downstream of it, including the subtitle track fetch) stops
  re-resolving and re-fetching the same trailer repeatedly.
2026-07-29 16:22:38 +03:00
KhooLy
329d50cf9f fix: shrink hero trailer subtitle overlay and remove debug logging
Confirmed the YouTube auto-translation fallback works (429s are just
transient rate limiting, retried successfully). The overlay itself was
sized/positioned for a shorter hero (13rem from viewport bottom, up to
1.5rem font) and now overlaps the ratings/genre row below the video.
Shrunk font size, padding, and max-width, and moved it down near the
true bottom edge like a normal caption bar.
2026-07-29 16:22:38 +03:00
KhooLy
5335cea6b8 fix: try every YouTube trailer candidate for hero autoplay, not just the first
Hero autoplay only ever attempted the first YouTube URL found in
displayTrailers. If that specific video was region-blocked (common
when a title has multiple trailers from different platforms/regions,
e.g. Netflix uploads that are geo-restricted while a later Crunchyroll
one isn't), resolveYoutubeTrailer failed and autoplay silently gave up
instead of falling through to a working trailer. Now it walks the full
list of extracted YouTube ids in order and uses the first one that
actually resolves to a stream.

Also reverts the cast/crew effect dependency change from the previous
commit — that "fix" was based on a wrong theory and broke cast/crew
entirely (displayMeta only carries the addon summary, no cast data,
until detail.meta finishes loading; keying on id/type alone meant the
effect never re-ran once the full cast data arrived).
2026-07-29 16:22:38 +03:00
KhooLy
bc154b6454 fix: stop rating/trailer updates from restarting the cast/crew fetch
The cast-building effect depended on the whole displayMeta object,
which gets a fresh reference on every state snapshot (JSON.parse never
preserves identity) — including the ones that now fire automatically
after the detailSecondaryRequested auto-dispatch fix. Each secondary
data arrival (ratings, trailers, fanart) was cancelling and restarting
the in-flight cast/director fetch, sometimes before TMDB photos or the
full crew list resolved. Cast/crew content only depends on the title
itself, so key the effect on displayMeta.id/type instead, matching the
sibling peopleImages effect right below it.
2026-07-29 16:22:38 +03:00
KhooLy
c655db72af fix: make cast avatar photos circular, matching the initials placeholder 2026-07-29 16:22:38 +03:00
KhooLy
8446c28e2d feat: use Rotten Tomatoes' real Tomatometer/Popcornmeter icon states
Replace the recolored generic tomato/popcorn placeholders with RT's
actual fresh/rotten/empty icon artwork (extracted from the live DOM
of rottentomatoes.com/about's score-icon-critics/score-icon-audience
components: full multi-layer SVGs including the tomato's stem and the
popcorn bucket's kernels/highlight stripes, not just the base shape).

tomatoes/popcorn now swap between dedicated fresh/rotten SVG files via
iconForValue instead of CSS-masking a single monochrome icon, so no
color-matching guesswork is needed anymore — the artwork already has
the right colors baked in.
2026-07-29 16:22:38 +03:00
KhooLy
de69b98961 feat: color Metacritic badges by score, matching Metacritic's own convention
Critic score (0-100): green >=61, yellow 40-60, red <40.
User score (0-10): green >=7.5, yellow >=5.0, red below that.
2026-07-29 16:22:38 +03:00
KhooLy
9c0589e811 fix: auto-fetch detail secondary data and improve rating badge readability
detailSecondaryRequested (trailers/similar items/OMDb/MDBList ratings/
fanart) was only ever dispatched from the "similar titles" source
picker's onChange — never automatically on page load. Added an effect
in DetailScreen that fires it once per title, matching the existing
detailLoadRequested effect.

Rotten Tomatoes and Metacritic logos come from monochrome single-path
SVGs (no embedded brand color, unlike imdb/tmdb/trakt/letterboxd/mal),
so they rendered as illegible black icons. Recolor them via CSS mask
with their brand hex instead. Swap the Rotten Tomatoes audience score
("popcorn") from a recolored tomato icon to lucide-react's actual
Popcorn icon, matching Rotten Tomatoes' own Tomatometer/Audience Score
iconography. Also bumped badge size and added a background chip for
contrast against arbitrary backdrop images.
2026-07-29 16:22:37 +03:00
KhooLy
060f319340 fix: don't let a plugin-storage hydration failure abort the whole boot sequence
hydratePluginsFromStorage was throwing (Object.entries on an undefined
scraperOverrides) inside the single top-level try/catch in useAppInit's
boot effect. That silently aborted everything after it in the same
try block — loadPrefs(), the settings.values hydration, and
libraryHydrateRequested — leaving state.settings.values at its default
for the whole session. Every pref read through appPrefs(state)
(detail view mode, MDBList/OMDb/TMDB API keys, etc.) fell back to its
hardcoded default until the user touched Settings and triggered a
live settingsChanged dispatch.

Isolated hydratePluginsFromStorage in its own try/catch so a failure
there can't take down prefs hydration, and hardened
hydratePluginsFromStorage itself against a missing scraperOverrides/
repositoryUrls so this specific crash can't recur either.
2026-07-29 16:22:37 +03:00
KhooLy
d51d1b9da5 fix: harden MDBList ratings fetch and match TMDB's id-resolution convention
fetchMdblistRatings previously required an explicit "tmdb:" prefix to
recognize a TMDB-sourced id, missing the bare-numeric-id convention
resolveTmdbId already uses elsewhere in this file — anime titles in
particular commonly carry bare numeric ids. Also wrap the whole
function in try/catch: an unhandled coreInvoke rejection here (e.g.
while fluxa-core hasn't been rebuilt with the new mdblist methods yet)
was taking down the entire Promise.all in fetchDetailSecondary,
silently breaking trailers/similar-items/OMDb ratings too.
2026-07-29 16:22:37 +03:00
KhooLy
a0a2698cb5 feat: show MDBList rating logos on the detail screen
Adds an MDBList API key setting; when configured, the detail screen
(both modern and classic layouts) renders a row of logo+score badges
for every source MDBList returns (IMDb, TMDB, Trakt, Letterboxd,
Rotten Tomatoes critic/audience, Metacritic critic/user, MyAnimeList)
instead of the plain-text IMDb/RT/Metascore line. Falls back to the
existing OMDb-based text rendering when no MDBList key is set.

Adds rottentomatoes.svg and metacritic.svg (simple-icons, matching
the existing trakt.svg/simkl.svg convention) to round out the logo
set alongside the already-present imdb/tmdb/trakt/letterboxd/mal.
2026-07-29 16:22:37 +03:00
KhooLy
35497a274c fix(desktop): stop running the Nuvio boot sync twice on every launch
useNuvioConnectivity's first health-check-passes branch ran its own
full importNuvioProfileData + home reload on mount, duplicating
useAppInit's syncExternalOnStartup which already does the exact same
full sync at boot for the same profile. Both ran concurrently on
every launch, doubling network cost and forcing two full home
reloads back to back — the source of the several-second wait after
opening the app. The connectivity hook now only acts on actual
down/up transitions during the session, not on its own initial check.
2026-07-28 23:15:50 +03:00