Merged
Conversation
…tructure Add per-user plugin instances with OAuth and simple credential support. This enables users to independently enable, configure, and authenticate with user-type plugins (e.g., AniList, MAL). - Migration for user_plugins and user_plugin_data tables with indexes - SeaORM entities with helper methods (OAuth expiry, health checks) - UserPluginsRepository: CRUD, credential encryption, health tracking - UserPluginDataRepository: key-value storage with TTL and upsert - PluginManager: user plugin handle spawning with credential injection
…oints Plugin system: OAuth & Authentication Flow. - Add OAuthConfig and PluginManifestType to plugin manifest protocol - Implement OAuthStateManager with CSRF state, PKCE S256, and 5-min TTL - Add token refresh service with automatic pre-expiry refresh - Create 6 user plugin API endpoints (list, enable, disable, disconnect, oauth/start, oauth/callback) - Add DTOs for user plugin management responses - Extend AppState with oauth_state_manager - Add get_oauth_refresh_token to UserPluginsRepository - Add json feature to reqwest for OAuth token exchange
…DK support User plugin system: Implement per-user key-value storage that plugins can use to persist data like taste profiles, sync state, and cached recommendations. Storage protocol (storage.rs): - JSON-RPC types for get/set/delete/list/clear operations - Method name constants and is_storage_method() helper Bidirectional RPC (rpc.rs, storage_handler.rs): - StorageRequestHandler processes plugin-to-host storage requests - RPC response reader detects incoming requests vs responses - Storage requests handled against the database, responses written back Plugin handle integration (handle.rs, manager.rs): - PluginHandle accepts optional StorageRequestHandler - Manager creates storage-scoped handles per user_plugin_id - Architectural isolation: plugins can only address their own data Background cleanup (cleanup_plugin_data.rs, types.rs, worker.rs): - CleanupPluginData task type for expired entry removal - Handler delegates to UserPluginDataRepository::cleanup_expired() TypeScript SDK (storage.ts): - PluginStorage class with get/set/delete/list/clear methods - Injectable write function for testability - handleResponse() for delivering host responses to pending requests - StorageError class with code and data fields
…PI registration User plugin system implementation: Backend: - Add GET /user/plugins/:id and PATCH /user/plugins/:id/config endpoints - Add build_user_plugin_dto helper for consistent DTO construction - Register all user plugin endpoints and DTOs in OpenAPI spec - Add 21 integration tests covering auth, CRUD, isolation, and config validation Frontend: - Add typed API client with 7 methods (list, get, enable, disable, updateConfig, disconnect, startOAuth) - Add ConnectedPluginCard and AvailablePluginCard components with health badges, sync status, and capability tags - Add OAuth popup flow hook (useOAuthFlow) and callback handler (useOAuthCallback) - Add Integrations settings page with connected/available sections, disconnect confirmation modal - Add /settings/integrations route and sidebar navigation - Add 28 component and page tests
…st reference plugin User plugin system: Sync Provider Protocol & Reference Plugin Backend: - Add sync protocol types (SyncPushRequest/Response, SyncPullRequest/Response) - Add sync API endpoints (trigger sync, push, pull, status) on user plugins - Add UserPluginSyncHandler background task for async sync operations - Add sync service module with protocol orchestration logic SDK (TypeScript): - Add 12 sync types matching Rust protocol (SyncEntry, SyncProvider, etc.) - Add createSyncPlugin factory for building sync-capable plugins - Add sync method routing in plugin server (getUserInfo, push, pull, status) AniList Sync Plugin (reference implementation): - GraphQL client for AniList API (viewer, manga list, save entry) - Bidirectional sync: push/pull reading progress, scores, dates, status - Score format conversion (POINT_100/10/5/3 ↔ 1-10 scale) - Status mapping (CURRENT/COMPLETED/PAUSED/DROPPED/PLANNING)
…st reference plugin
User plugin system - the recommendation engine.
Backend:
- Add recommendation JSON-RPC protocol types (get, updateProfile, clear, dismiss)
- Add API endpoints: GET /user/recommendations, POST refresh, POST dismiss
- Add UserPluginRecommendations background task type
Frontend:
- Add full Recommendations page with loading, error, 404, and empty states
- Add RecommendationCard (detail) and RecommendationCompactCard (carousel) components
- Add RecommendationsWidget on Home page using HorizontalCarousel
- Add /recommendations route and sidebar navigation link
Plugin SDK:
- Add RecommendationProvider interface and createRecommendationPlugin() factory
- Add recommendation protocol types matching Rust definitions
Reference plugin:
- Create recommendations-anilist plugin using AniList community recommendations
- Matches library entries to AniList IDs, fetches and scores recommendations
- Rename anilist-sync to sync-anilist to follow {capability}-{source} convention
…rplate Move SyncProvider and RecommendationProvider interfaces into types/capabilities.ts alongside MetadataProvider/BookMetadataProvider. Move sync and recommendation protocol DTOs into types/sync.ts and types/recommendations.ts respectively, deleting the top-level files. Extract shared createPluginServer() with a MethodRouter pattern, replacing three copy-pasted handleLine/handleRequest implementations with one shared base. Relocate sync.test.ts into types/ to match its source files. Update imports in reference plugins (sync-anilist, recommendations-anilist).
Allow metadata plugins to return external service IDs (AniList, MAL, MangaDex, etc.) alongside their metadata using the `api:` prefix convention. This enables sync and recommendation plugins to match series directly by ID instead of falling back to title-based search. - Add PluginExternalId type and externalIds field to protocol - Add MetadataWriteExternalIds permission - Store cross-reference IDs in series_external_ids during metadata apply - MangaBaka plugin now returns api:anilist, api:myanimelist, etc. - AniList recommendations plugin prefers api:anilist source
The similarity() function gave a flat 0.8 score for any substring containment, causing popular titles like "Air Gear" to outscore exact matches like "Air". Replace with length-ratio-adjusted containment scoring (0.8 × shorter/longer) combined with Jaccard word overlap, returning the higher of the two. Also update scoreResult() to check alternateTitles for both similarity and the exact-match bonus, handling cases where the primary title differs (e.g., "AIR (TV)") but an alternate title matches exactly.
Add externalIdSource capability to plugin manifest so sync providers can declare which external ID source (e.g., "api:anilist") their entries correspond to. During sync pull, entries are now matched to Codex series by looking up the series_external_ids table using this source. - Add find_by_external_id_and_source to SeriesExternalIdRepository - Add external_id_source field to PluginCapabilities and DTO - Add match_pulled_entries to sync handler with matched count tracking
…cProvider Remove the deprecated `syncProvider` field from the SDK PluginCapabilities type and the unused `LegacyPluginCapabilities` interface. Update frontend types, components, and tests to use `userSyncProvider` consistently, matching the backend DTO which already uses the new name.
… types Replace 15+ manually-defined TypeScript interfaces across API clients, mock factories, and components with aliases to OpenAPI-generated types. - Remove redundant MockLibrary/MockSeries/MockBook type aliases - Replace duplicated PaginationLinks/PaginatedResponse with centralized imports - Replace manual interfaces in userPlugins, plugins, and books API clients - Replace inline component type extractions with imports from @/types - Add missing schema fields to mock factories (analysisError, progressPercentage, minValue/maxValue) - Fix syncProvider field name to match generated UserPluginCapabilitiesDto - Fix IntegrationsSettings tests to match actual component text
…modal Move permissions, scopes, and library filter configuration out of the Add/Edit Plugin modal into the existing search configuration modal, which is renamed from SearchConfigModal to PluginConfigModal. The configure modal now dynamically shows tabs based on plugin capabilities from the manifest: - Permissions tab: always shown, with capability-filtered permissions and scopes (metadata providers see metadata:* permissions and series/book scopes; sync providers see library:read and sync scopes) - Template/Preprocessing/Conditions tabs: only for metadata providers - Plugins with no manifest show all permissions with a warning The Add/Edit Plugin modal is simplified to only connection setup (General, Execution, Credentials tabs), making it the minimum needed to connect a plugin and retrieve its manifest.
…ebase "Sync Provider" implied the plugin provides/sources data, but it actually pushes reading progress to external services like AniList. Rename to "userReadSync" / "readSync" to better reflect the capabilitys purpose. Also updates the UI badge label from "User Sync Provider" to "Reading Sync".
…t sync User-type plugins (sync, recommendations) were invisible on the Integrations page because plugin_type defaulted to "system" at creation time and was never updated from the manifest. Now update_manifest() parses capabilities and automatically sets plugin_type to "user" when userReadSync or recommendationProvider is declared, or "system" when metadataProvider is present.
…nProvider across codebase Align the recommendation capability name with the established convention where user-scoped plugin capabilities use the "user" prefix (matching userReadSync), while system-level capabilities like metadataProvider do not. Updates Rust backend, TypeScript SDK, plugin examples, frontend components, tests, documentation, and regenerated OpenAPI types.
7190575 to
2016ec9
Compare
…er plugin settings Implement functional reading progress sync that applies pulled entries to Codex (marking books as read) and builds push entries from Codex progress. Add sync mode control (pull/push/both) via user settings. Add personal access token authentication as an OAuth alternative, with a credentials endpoint and token input UI. Add per-user plugin settings modal with schema-driven fields and sync mode selector. Improve OAuth flow: callback returns self-closing popup HTML, redirect URI resolved from Origin header with fallback, and URI persisted in pending flow for token exchange consistency. Add admin OAuth config (client ID/secret) in plugin config modal. Enhance metadata preview: sort arrays before diff comparison, add external IDs preview, filter links/ratings to plugin-provided sources. Enrich plugin UI with sync metrics, last sync results, user counts, setup instructions, and live status refresh.
…edential delivery - Add PushConfig (progressUnit, pushCompletedSeries, pushInProgressSeries, pushInProgressVolumes) parsed from user plugin config, with matching userConfigSchema fields in sync-anilist manifest - Push volumes instead of chapters by default to avoid misleading AniList "Read chapter X" activity entries - Pull apply now prefers volumes over chapters when determining progress - Mark series as Completed only when metadata total_book_count is known and all books are read; otherwise always push as Reading - Default credential_delivery to init_message (SDK plugins read from params.credentials) and auto-set it when manifest declares requiredCredentials or oauth - Persist manifest on user plugin start so userConfigSchema is available - Fix sync result display to show "Sync completed" when all counts are zero
…mplify pull query Surface pull/push errors in sync results so the UI can display partial failures with orange notifications instead of false successes. Record failure status on the user plugin instance when errors occur. Remove the unused updatedSince/since filtering from AniList pull queries (full pull is always used). Improve sync mode description with highest-progress-wins semantics and add "(recommended)" label. Add AniList Sync documentation page and comprehensive README covering sync modes, conflict resolution, completed-status logic, and push configuration options.
Wire up bidirectional rating/notes sync between Codex and AniList, controlled by a new syncRatings toggle (off by default). Push: batch-fetch user ratings and include score (1-100) and notes in push entries when enabled. Pull: import AniList scores and notes into Codex only when no existing local rating exists (Codex wins). Update score conversion functions to use Codexs native 1-100 scale instead of the previous 1-10 intermediate scale, and move them to anilist.ts for testability. Remove redundant scoreFormat config field since its auto-detected from the users AniList profile.
Add configurable staleness thresholds for in-progress series during AniList push sync. Series with no reading activity beyond the threshold are automatically set to Paused or Dropped on AniList. - Add pauseAfterDays and dropAfterDays user config fields to manifest - Add apply_staleness_status() helper with drop-before-pause priority - Track latest_updated_at across books in build_push_entries() - Handle both string and numeric config values from frontend - Only apply to series with Reading status (completed series unaffected) - Add unit tests for staleness logic, config parsing, and integration - Update README with auto-pause/auto-drop documentation
…y data builder - Fix UserPluginCard to hide Sync button for non-sync plugins and Settings button when no configurable fields exist - Add Recommendations badge and View link for connected recommendation plugins - Conditionally show Recommendations nav in sidebar only when user has a connected recommendation plugin - Register UserPluginRecommendationsHandler in task worker for background refresh tasks (clear cache + pre-generate results) - Implement build_user_library() to batch-fetch series, metadata, genres, tags, external IDs, reading progress, and ratings for plugin consumption - Pass real library data to recommendations/get instead of empty array - Add clear() method to AniList recommendation plugin to reset dismissed list
4488ace to
6bdeab0
Compare
User plugin system improvements: - Split InitializeParams.config into adminConfig + userConfig with backward-compatible merged config field (deprecated) - Add latestUpdatedAt to SyncEntry for time-based plugin logic - Add totalChapters/totalVolumes to SyncProgress - Fix externalIds naming inconsistency (id → externalId) in SDK - Export missing OAuthConfig from SDK types - Fix shutdown type hack in SDK server (null as unknown → proper null) - Add serialization tests for InitializeParams and SyncProgress
… server/plugin concerns Replace PushConfig (7 plugin-specific fields) with CodexSyncSettings (4 generic fields) stored under config._codex namespace. This cleanly separates what the server controls (filtering, progress counting, ratings) from what plugins own (progressUnit, staleness thresholds). Server changes: - Always send books-read as volumes (removed progressUnit branching) - Populate totalVolumes from series metadata total_book_count - Remove apply_staleness_status() — plugins handle staleness using latestUpdatedAt - Read settings from _codex namespace instead of plugin config keys - Default sync_ratings to true (was false) Plugin manifest (sync-anilist): - Remove 4 server-interpreted fields: syncRatings, pushCompletedSeries, pushInProgressSeries, pushInProgressVolumes - Keep 3 plugin-specific fields: progressUnit, pauseAfterDays, dropAfterDays Frontend (UserPluginSettingsModal): - Add "Sync Settings" section with 4 Codex toggles for all sync plugins - Add "Plugin Settings" divider separating Codex from plugin-specific config - Store Codex settings in config._codex.* namespace
… hardening sync-anilist: read userConfig in onInitialize, use progressUnit during push, implement applyStaleness() for auto-pause/drop, add rate limit retry (wait + retry once on 429), pre-fetch existing media IDs to distinguish created vs updated in push results, update README. recommendations-anilist: validate and clamp maxRecommendations from adminConfig, handle empty library gracefully, add HTML entity decoding to stripHtml. Frontend: add PluginsSettings tests and OAuthFlow tests, fix cross-test isolation with icon-class-based button selection.
…sync Add batch repository methods for books, read progress, series external IDs, and user plugin data that return HashMap-grouped results using is_in() queries. Refactor build_push_entries and match_and_apply_pulled_entries to pre-fetch all data upfront instead of querying per-item in loops. Optimize list_user_plugins to batch-fetch sync results for all plugins at once. Includes unit tests for the new batch methods covering empty inputs, multi-record grouping, filtering, and expiry behavior.
Wire the existing token_refresh module into create_user_plugin_config so OAuth tokens are automatically refreshed before spawning user plugins. - Add ensure_fresh_oauth_token() to PluginManager with helper methods for extracting OAuth config from manifest and plugin config - Add TokenRefreshFailed and ReauthRequired error variants with user-facing messages in sanitize_plugin_error() - Remove #[allow(dead_code)] from token_refresh module - Fix unused import (waitFor) and missing import (afterEach) in OAuthFlow.test.tsx
…rage, add OAuth timeout User plugin system improvements: - Split user_plugin_sync.rs into 5-file module: mod.rs, settings.rs, push.rs, pull.rs, tests.rs - Split PluginsSettings.tsx into sub-components: PluginForm, PluginDetails, PluginFailures, types - Wire PluginStorage into SDK createPluginServer — route storage responses in handleLine, inject storage into InitializeParams, cancel pending requests on shutdown - Update recommendations-anilist to persist dismissed IDs via PluginStorage (write-through cache for durability across restarts) - Add 5-minute OAuth popup timeout with user notification - Cache deserialized plugin manifests in user_plugins handler - Fix SDK README: remove non-existent contentType parameter - Document protocol versioning strategy and _codex sync settings
…rage quotas, and rate limiting Wire OAuthStateManager.cleanup_expired() into the CleanupPluginData task handler so expired OAuth flows are cleaned up on schedule instead of accumulating in memory. The OAuthStateManager is now created once and shared between the API state and task workers. Add storage quota enforcement to plugin storage writes: values are rejected if serialized size exceeds 1MB, and new keys are rejected when a plugin already has 100 keys. Upserts to existing keys are allowed even at the limit. Add per-user rate limiting to the OAuth start endpoint — users are limited to 3 concurrent pending OAuth flows via a new pending_count_for_user() guard. Introduces a TooManyRequests (429) variant to ApiError. Tests added for all three areas.
Prevent duplicate background tasks when users rapidly trigger sync or recommendation refresh. Add TaskRepository::has_pending_or_processing() that checks JSON params for matching plugin_id+user_id on pending/processing tasks, since plugin tasks store identity in params rather than FK columns. Both trigger_sync() and refresh_recommendations() now return 409 Conflict when a task is already queued or running. Cross-user isolation is preserved — different users can sync the same plugin concurrently. Tests added for deduplication (same user blocked) and cross-user isolation (different users allowed).
…ath tests Extract `to_recommendation_dto()` from inline closure in the recommendations handler to make the Recommendation → RecommendationDto field mapping independently testable. Add unit tests verifying the DTO conversion preserves all fields for both fully-populated and minimal recommendations, plus a test for the complete RecommendationsResponse JSON shape including camelCase serialization and skip_serializing_if behavior. Add integration tests covering error paths: graceful 500 when a connected plugins process cant be spawned (for both GET and dismiss), request parsing for dismiss without a reason, and acceptance of all valid dismiss reason strings.
…configurable seeds Add 30-second AbortSignal timeouts to all AniList API calls in both sync-anilist and recommendations-anilist plugins, preventing hung requests from blocking task execution indefinitely. Refactor recommendations-anilist client to use the same query/executeQuery pattern as sync-anilist, adding retry-once-on-429 behavior that was previously missing (the sync plugin retried but recommendations threw immediately). Fix recommendation pagination — getRecommendationsForMedia() now fetches up to 3 pages instead of hardcoding page 1, so popular titles return more complete recommendation sets. Add configurable maxSeeds to the recommendations manifest config schema (default 10, range 1-25), replacing the previously hardcoded value. Tests added for timeout wrapping, retry behavior, and pagination logic.
…nerate OpenAPI recommendation types Extract PluginConfigModal.tsx (903 lines) into focused tab components under plugin-config/: PermissionsTab, OAuthTab, TemplateTab, PreprocessingTab, ConditionsTab, plus a shared types module for capability helpers, permission/scope data, and template logic. Register recommendation DTOs and endpoints in the OpenAPI doc struct so they appear in the generated spec. Replace the five manually-defined TypeScript interfaces in recommendations.ts with re-exports from the generated api.generated.ts, matching the pattern used by plugins.ts.
… documentation Add three new sections to the user-facing plugin documentation: - Security Model: covers AES-256-GCM token encryption, per-user data isolation, process sandboxing (command allowlist, env blocklist, 30s timeouts), OAuth protections (CSRF state, PKCE S256, rate limiting), and storage quotas (100 keys, 1MB/value) - Privacy & Data Handling: documents what data leaves Codex by plugin type, what is stored locally (encrypted), and how to disconnect and delete all plugin data - Troubleshooting OAuth Connections: covers popup blocked, redirect URI mismatch, expired tokens, external rate limits, connection timeouts, and too-many-attempts errors Also expand the security summary in the developer plugin overview with the new details and a cross-link to the full security model.
…rings with constants Remove deprecated code from the TypeScript plugin SDK and replace magic string literals with named constants across Rust and TypeScript. - Remove deprecated `config` field from InitializeParams (use adminConfig/userConfig instead) and update all 4 consuming plugins - Remove deprecated `createSeriesMetadataPlugin` factory function and `SeriesMetadataProvider`/`PartialSeriesMetadataProvider` types - Add CODEX_CONFIG_NAMESPACE constant in Rust (settings.rs) - Add LAST_SYNC_RESULT_KEY constant in Rust (mod.rs, user_plugins.rs) - Add EXTERNAL_ID_SOURCE_ANILIST constant in TypeScript SDK and use it in sync-anilist and recommendations-anilist plugins
…ogic tests Cover the previously untested core recommendation generation logic in the AniList recommendations plugin. Export convertRecommendations(), resolveAniListIds(), and dismissedIds for direct unit testing with a setClient() helper to inject mock AniList clients. Tests cover: - convertRecommendations: score calculation weights, null/falsy averageScore fallback, rating clamping, exclude/dismiss filtering, inLibrary flag, null mediaRecommendation, field mapping, and basedOn construction - resolveAniListIds: external ID sources (api:anilist, legacy variants), title search fallback, non-numeric ID handling, and uninitialized client - Score merging: duplicate boost (+0.05), clamp at 1.0, basedOn dedup, reason text update, and triple-recommendation accumulation - stripHtml: nested tags and br-with-space variant
…ssification and add circuit breaker
Replace the fragile `err_str.contains("invalid_grant")` pattern in token
refresh with proper HTTP status code and RFC 6749 error body parsing.
Add a `TokenRefreshError` enum that classifies OAuth failures into four
categories: ReauthRequired (HTTP 401, or 400 with invalid_grant/
invalid_client/unauthorized_client), RateLimited (429), Temporary
(server errors), and Network (transport failures). The refresh function
now returns this structured error directly instead of an anyhow string.
Add a circuit breaker that skips token refresh entirely when a user
plugin has 3+ failures within the last hour, immediately requiring
re-authentication instead of hammering a broken OAuth provider.
Tests added for all error classification paths and circuit breaker
state transitions.
Replace in-memory O(n) filtering in `has_pending_or_processing` with a single database query using JSON operators. Previously, the method loaded all pending/processing tasks of a given type via `.all(db)` and filtered in Rust — this wont scale as the task queue grows. Now uses `SELECT 1 ... LIMIT 1` with backend-specific JSON extraction: - SQLite: `json_extract(params, $.plugin_id)` - PostgreSQL: `params->>plugin_id` Tests added covering match/no-match for plugin_id, user_id, task_type, task status transitions, and both sync and recommendation task types.
…ed annotations Remove module-level #![allow(dead_code)] from protocol.rs, recommendations.rs, and the impl-level blanket on CredentialEncryption. Replace with targeted #[allow(dead_code)] on each individual unused item, each accompanied by a justifying comment explaining why it exists (protocol contract type, standard JSON-RPC error code, operational utility, etc.). In the TypeScript SDK, replace `as unknown as` double type assertions in storage.ts with proper single-step type narrowing using `obj.error as JsonRpcError` and direct property access for `obj.result`.
…gin system Replace outdated plugin dev docs with comprehensive, current documentation covering all three plugin types (metadata, sync, recommendations). writing-plugins.md: Full rewrite with "Build Your First Plugin" tutorial, sync and recommendation plugin patterns, storage API, OAuth configuration, error handling, common patterns (rate limiting, pagination, caching), and reference implementation table. overview.md: Update plugin list (add AniList Sync and Recommendations), remove "coming soon" labels, update architecture diagram to show all three plugin categories, add OAuth and storage quota details. protocol.md: Add scoped metadata methods (metadata/series/*, metadata/book/*), sync methods, recommendation methods, and bidirectional storage protocol. Update InitializeParams to use adminConfig/userConfig/credentials fields. sdk.md: Document all three factory functions (createMetadataPlugin, createSyncPlugin, createRecommendationPlugin), all provider interfaces, and complete type reference including sync, recommendation, and book types.
Add a "Plugin Credential Encryption" section to the configuration guide covering CODEX_ENCRYPTION_KEY generation, what it protects (OAuth tokens, refresh tokens, plugin credentials), key requirements (32-byte base64), and a 5-step manual key rotation procedure via disconnect/reconnect. Also add the encryption key to the environment variables reference, startup-time settings list, Kubernetes example, and security best practices. Cross-reference from the plugins security model page.
…rror type cast Remove all 5 blanket #![allow(dead_code)] directives from plugin infrastructure files and surgically delete the dead code they were hiding. health.rs: Remove HealthStatus, HealthState, HealthMonitor, and unused HealthTracker methods — keep only the core failure counting used by PluginHandle (new, record_success, record_failure, should_disable). handle.rs: Remove dead methods (health_state, is_running, is_disabled, restart, enable) and unused error variants (HealthCheckFailed, InvalidManifest). process.rs: Remove dead methods (envs, wait, is_running, pid) and unused error variants (LineTooLong, OutputTooLarge, WriteFailed, ReadFailed, ExitCode). rpc.rs: Remove dead methods (notify, is_running, pid) and unused trace import. manager.rs: Remove dead methods (get_plugin, all_plugins, health_checks_running, metrics_service getter), unused error variants (NoPluginsForScope, UserPluginNotAuthenticated), and dead UserPluginContext fields. plugin_actions.rs: Remove match arms for deleted error variants. Recommendations.tsx: Replace unsafe `as unknown as ApiError` double-cast with proper useQuery<RecommendationsResponse, ApiError> generic typing.
…ns-anilist Add a per-user `searchFallback` boolean setting to the recommendations-anilist plugin that controls whether title-based AniList search is used when a series has no external ID. Default is `true` to preserve existing behavior. Previously, `resolveAniListIds()` always fell back to `client.searchManga()` for entries without an AniList external ID, which could produce incorrect matches for series with ambiguous titles. Users now have a toggle to disable this and restrict matching to explicit external IDs only. - Declare `searchFallback` in `userConfigSchema` (manifest) - Read the setting from `params.userConfig` during `onInitialize` - Gate the `searchManga()` call in `resolveAniListIds()` behind the flag - Export `setSearchFallback()` test helper for upcoming unit tests
Add title-based AniList search as a fallback when series lack external IDs. The server now sends unmatched series (with titles) to the plugin when `_codex.searchFallback` is enabled, and the plugin resolves them via AniLists search API before pushing progress. - Add `title` field to SyncEntry (Rust + TypeScript SDK) - Add `searchManga()` to sync-anilist AniListClient - Add `searchFallback` to sync-anilist manifest userConfigSchema - Gate title search behind `searchFallback` config in push handler - Add `search_fallback` to CodexSyncSettings (`_codex` namespace) - Build unmatched series entries in push builder when fallback enabled
Add comprehensive test coverage for the search fallback feature across both TypeScript plugins and Rust backend: - recommendations-anilist: test searchFallback toggle enabling/disabling searchManga calls, external ID resolution bypassing search, and mixed matched/unmatched entry handling - sync-anilist: test pushProgress with searchFallback enabled/disabled, search returning null, valid externalId skipping search, and created/updated status reporting - sync-anilist AniListClient: test searchManga success, null Media, API error swallowing, and network error swallowing - Rust SyncEntry: test title field serialization, omission when None, deserialization with and without title - Rust push builder: test build_push_entries with search_fallback including/excluding unmatched series, title population for both matched and unmatched entries - Rust settings: test CodexSyncSettings search_fallback default, enabled, and disabled parsing Export test helpers (setClient, setViewerId, setSearchFallback, provider) from sync-anilist/src/index.ts to enable direct pushProgress testing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.