Commit graph

20 commits

Author SHA1 Message Date
KhooLy
d33fd06fc9 chore: consolidate production hardening updates 2026-08-01 18:53:26 +03:00
KhooLy
23b3a697df perf: add incremental engine dispatch metadata 2026-07-31 21:59:22 +03:00
KhooLy
cd225d3936 fix(core): thread mdblistRatings through the detail effect-completion merge
DetailState (headless_engine/detail.rs) whitelists which fields from a
completed fetchDetailSecondary effect get copied into engine state —
mdblistRatings wasn't in that list, so the frontend's fetch always
succeeded but the value was silently dropped before ever reaching the
UI. Added alongside the existing omdb_ratings field it mirrors.
2026-07-29 03:08:20 +03:00
KhooLy
fffbe628c7 Add shared cross-platform domain policies 2026-07-22 16:56:28 +03:00
KhooLy
fe817cb41d feat: extend core integrations 2026-07-22 01:07:50 +03:00
KhooLy
e69c035e59 move AniList anime-tracking detection and match policy into core
Desktop's anime-tracking gate, link-based AniList ID extraction, and
search-result best-match selection were duplicated logic Android lacks
entirely (it only tracks progress when a meta id is already prefixed
anilist:). Porting the decision logic here — while keeping GraphQL I/O
and caching on the platform side — lets both platforms share it.
2026-07-18 23:52:28 +03:00
KhooLy
a92a25c662 add shared business policy for addon priority, home ranking, scrobble, and collection sources
Extends existing cross-platform domain modules (nuvio_sync, addon_store,
home_ranking, watchlist_plan, external_sync, player_policy, player_scrobble,
calendar_plan, search_plan) with logic previously duplicated or missing on
the desktop side, so it's implemented once and reused across platforms.
2026-07-18 23:37:45 +03:00
KhooLy
e1a3b2445a deduplicate shared business logic 2026-07-18 21:47:23 +03:00
KhooLy
491868fa22 move remaining desktop policy into core 2026-07-18 21:04:54 +03:00
KhooLy
25b3ca73a0 Read removed/_mtime from Stremio library items; add watchlist extraction
Kotlin's LibraryItem DTO never declared removed/_mtime, so even though
this file already worked on raw JSON Values, those fields never
survived the trip from the Stremio datastore response. Adds
libraryWatchlistItems (id/name/type/poster/background/updatedAtMs,
excluding removed items) so the pull side can finally see Stremio
library membership as a timestamped list, matching the shape the
mergeWatchlistTimestamped bridge expects. Also fixes
libraryContinueWatchingItems, which never checked "removed" at all --
a removed item with leftover progress state could still surface as
continue-watching.
2026-07-18 20:11:15 +03:00
KhooLy
d7def2c615 Add core_invoke method to build AniList SaveMediaListEntry variables
AniList integration has been pull-only — no mutation is ever sent, so
watchlist/mark-watched changes made in Fluxa never reach AniList, even
though its OAuth token already has full read/write access (no scope
restriction). Adds a pure function that extracts the numeric media id
from the "anilist:<id>" content id scheme and builds the GraphQL
mutation variables, so the Kotlin side only needs to POST the
variables through the existing generic anilistGraphQl call.
2026-07-18 19:26:06 +03:00
KhooLy
3f68a26cbf Add generic timestamp-based membership merge for watchlist/watched sync
Trakt, Simkl, and Stremio currently reconcile watchlist and watched
state as blind set-unions (merge_external_watchlist_json/merge_external_watched_json)
with no notion of "which side changed more recently." Nuvio already
resolves this kind of conflict with timestamp comparisons; this adds
the same capability as a provider-agnostic core_invoke method
(mergeWatchlistTimestamped/mergeWatchedTimestamped) so Trakt/Simkl/
Stremio can reuse it instead of duplicating Nuvio-specific logic.

Takes local items (id, updatedAt, active) and remote items (id,
updatedAt), and returns which ids to apply locally vs. push to the
remote, picking a winner per id by comparing timestamps.
2026-07-18 18:42:38 +03:00
KhooLy
e68dcc405c Add reusable calendar planning and profile-aware library commands 2026-07-18 16:52:14 +03:00
KhooLy
256a0aeaee Route fluxa_core JNI functions through core_invoke
Retire hand-written per-function JNI bindings in fluxa-core in favor of
the generic core_invoke(method, args_json) dispatcher already exposed
via UniFFI. Every stateless domain function (calendar, watchlist, addon
parsing, trakt/simkl sync, home ranking, player policy, etc.) is now
reachable by method name through core_invoke; jni.rs keeps only the
coreInvoke passthrough and the handle-lifecycle functions that manage
opaque stateful engine/state handles, which don't fit the stateless
JSON-in/JSON-out shape.
2026-07-18 15:00:58 +03:00
KhooLy
f7920e3d13 Track per-addon stream failures so the UI can surface them instead of silently dropping results
Adds detail.failedAddons, populated from the fetchDetailStreams
completion payload, distinct from availableAddons so a permanently
failed addon (exhausted retries, bad response, etc.) can be shown to
the user as a failure rather than looking identical to "no streams".
2026-07-11 13:19:22 +03:00
KhooLy
12fc4398e6 Fix core CI checks 2026-07-11 00:03:03 +03:00
KhooLy
c2b0e610ec Plan Discover per selected catalog 2026-07-10 13:53:39 +03:00
KhooLy
90d13d9983 Consolidate addon resource parse+plan into a single core call
Extracts parse_addon_body (returns a ParsedAddonBody enum instead of
always serializing to a JSON string) so the existing three-hop path
(parseAddonResourceResult -> wrapAddonResourceResponse ->
resourceParsePlan, still used by subtitles.ts) and the new single-call
parseAndPlanAddonResource share the same parsing logic instead of
duplicating error-envelope construction.

Removes the now-redundant wrapAddonResourceResponse FFI method. Tests
assert the combined call produces byte-identical output to the old
three-step pipeline.

Pairs with fluxa-desktop's matching addonManifest.ts/fetchPlanning.ts
change.
2026-07-09 21:29:29 +03:00
KhooLy
4ce837b125 test(ffi): add checked-in core_invoke method fixture + routing regression test
Per the robustness plan's §7. The plan's main suggestion — a
core_methods! table macro generating ffi.rs's route_* functions — was
skipped: ffi.rs's ~115 methods have heterogeneous call shapes (0-arg,
single string arg, multi-field object arg, some routed straight to a
domain function, some with inline argument massaging first), and
CLAUDE.md already records the project's own assessed judgment that
consolidating the three FFI surfaces isn't worth the rewrite risk —
that reasoning applies just as much to a macro-generated version of
one of those three surfaces.

Implemented the plan's other, explicitly "cheap middle ground"
suggestion instead: tests/wire/core_invoke_methods.txt checks in the
current list of all 114 method names route() recognizes (extracted
from ffi.rs's match arms), and a new test
(ffi::tests::every_known_core_invoke_method_still_routes) calls
core_invoke(method, "{}") for each and asserts the error kind isn't
unknown_method. This doesn't validate each method's business logic —
only that the name is still wired to some router — but that's exactly
enough to turn a renamed or deleted method from a runtime "no such
method" surprise on a platform this repo can't see into a test failure
here. Verified it actually catches drift by temporarily adding a bogus
method name to the fixture and confirming the test fails.

core_capabilities_json generation from the method table (the plan's
second §7 bullet) doesn't apply here: core_contract.rs's
CoreCapabilitySet is a small hand-curated set of platform feature
flags (http/storage/auth/player/plugins/torrent/local_stream/
notifications), not a per-method availability list — there's no
natural 1:1 mapping from the 114 routed methods to those 8 flags to
generate from.
2026-07-07 15:06:54 +03:00
KhooLy
2439bb3ba6 test: add golden wire fixtures and a headless-engine dispatch fuzz target
Per the robustness plan's §8: a tests/wire/ directory holds one real
AppAction input per representative action family, each paired with a
checked-in golden DispatchResult captured from an actual dispatch (a
new #[cfg(test)] wire_fixtures_match_golden_dispatch_output test
compares against it, with UPDATE_WIRE_FIXTURES=1 to regenerate when a
change is intentional). Any future camelCase/field drift on the
dispatch/completeEffect wire now fails in this repo instead of surfacing
as a silent Android/desktop regression.

Also add fuzz/fuzz_targets/engine_dispatch.rs, feeding arbitrary bytes
into both headless_engine_dispatch_json and
headless_engine_complete_effect_json against one engine handle — this
is the one path in the crate that runs global engine-mutating logic
without a catch_unwind guard on the desktop call path, so it's the
highest-value fuzz target missing from fuzz/. Exposes the four
headless_engine entry points as `pub` (still unreachable outside the
crate except through the fuzzing-gated `fuzz_targets` re-export module)
following the same pattern already used for parse_manifest and the
content_identity helpers.
2026-07-07 14:28:48 +03:00