Skip to content

Add Sync/Share tabbed settings UI and Intro Skipper-backed sharing with local dedupe#24

Merged
AbandonedCart merged 3 commits intomainfrom
copilot/add-sync-and-share-tabs
Apr 23, 2026
Merged

Add Sync/Share tabbed settings UI and Intro Skipper-backed sharing with local dedupe#24
AbandonedCart merged 3 commits intomainfrom
copilot/add-sync-and-share-tabs

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 23, 2026

This update introduces a dual-mode settings experience: Sync for existing enable/disable config management and Share for submitting enabled timestamps to https://db.skipme.workers.dev. Sharing now separates TV and movie payloads by endpoint contract and prevents duplicate re-submission using local history.

  • UI: tabbed Sync/Share workflow

    • Added two tabs over the same filtered library layout:
      • Sync: existing toggle + save behavior
      • Share: same filtered/toggled scope, but share action instead of save
    • Search/filter state now applies consistently in both tabs.
    • Added share loading/feedback states and tab accessibility updates.
  • Backend: Share API for filtered enabled items

    • Added POST /SkipMeDb/Share endpoint.
    • Request carries current filtered IDs and current enable/disable state from UI.
    • Server derives enabled candidates from that state and builds payloads by media type:
      • TV seasons → POST /v1/submit/season
      • Movies → POST /v1/submit/collection
  • Timestamp source + conversion parity with Intro Skipper

    • Share payloads are built from Intro Skipper DB timestamps (introskipper.db), using the same segment typing/mapping used for Jellyfin-facing segments.
    • Segment selection mirrors Intro Skipper behavior (earliest per segment type where multiple exist).
  • Local share-history dedupe in existing plugin DB

    • Extended existing skipme-segments.db schema with SharedUploads.
    • Added dedupe checks before upload: same item+segment where start_ms, end_ms, and duration_ms are each within ±1000ms is treated as already shared.
    • Successful uploads are persisted to this table for future dedupe.
  • New types/services

    • Added share request/response models and share submission service.
    • Registered share service in DI and wired web client API for share calls.

Example payload shaping (TV vs movies):

// TV: grouped by season for /v1/submit/season
[
  {
    "tvdb_series_id": 81189,
    "tvdb_season_id": 795657,
    "tmdb_id": 1396,
    "season": 1,
    "items": [ /* per-episode segment items */ ]
  }
]

// Movies: flat collection for /v1/submit/collection
[
  {
    "tmdb_id": 603,
    "imdb_id": "tt0133093",
    "segment": "intro",
    "duration_ms": 8160000,
    "start_ms": 5000,
    "end_ms": 95000
  }
]

Copilot AI and others added 3 commits April 23, 2026 00:53
[Route("SkipMeDb")]
public sealed class ShareController(ShareSubmissionService shareSubmissionService) : ControllerBase
{
private readonly ShareSubmissionService _shareSubmissionService = shareSubmissionService;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Redundant field assignment with primary constructor

C# 12+ primary constructor parameters are automatically captured. Explicit _shareSubmissionService field declaration creates unnecessary memory allocation and duplication. You can remove the field entirely and use the primary constructor parameter directly.

public sealed class ShareSubmitRequest
{
/// <summary>Gets the filtered series IDs currently visible in the UI.</summary>
public Collection<string> FilteredSeriesIds { get; } = [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Mutable request model properties

Getter-only Collection<T> properties are still mutable when deserialized by System.Text.Json. Request objects should be immutable. Use IReadOnlyCollection<T> with [JsonInclude].

public sealed class ShareSubmitResponse
{
/// <summary>Gets or sets a value indicating whether at least one submission request succeeded.</summary>
public bool Ok { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Mutable response DTO properties

Response objects should be immutable. Use init; instead of set; for all properties.

/// <param name="DurationMs">Item duration in milliseconds.</param>
public sealed record SharedUploadFingerprint(
Guid ItemId,
string Segment,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Segment should be strongly typed enum

Using string for known segment types prevents compiler validation, introduces case sensitivity bugs, and allocates unnecessary string copies.

serviceCollection.AddHttpClient(nameof(SkipMeApiClient));
serviceCollection.AddSingleton<SkipMeApiClient>();
serviceCollection.AddSingleton<SegmentStore>();
serviceCollection.AddSingleton<ShareSubmissionService>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Incorrect service lifetime

ShareSubmissionService depends on scoped/transient services like ILibraryManager but is registered as Singleton. This will cause captured disposed context bugs and memory leaks. Should be registered as Scoped.

@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented Apr 23, 2026

Code Review Summary

Status: 21 Issues Found | Recommendation: Address before merge

Fix these issues in Kilo Cloud

Overview

Severity Count
CRITICAL 5
WARNING 11
SUGGESTION 5
Issue Details (click to expand)

CRITICAL

File Line Issue
SkipMe.Db.Plugin/Models/ShareSubmitRequest.cs 14 Mutable request model properties - Getter-only Collection<T> properties are still mutable when deserialized
SkipMe.Db.Plugin/Services/SegmentStore.cs 227 Blocking database execution on request thread - ExecuteScalar() runs synchronously inside semaphore lock
SkipMe.Db.Plugin/Services/SegmentStore.cs 347 Missing PRIMARY KEY on SharedUploads table - No primary key prevents proper uniqueness constraints
SkipMe.Db.Plugin/Services/ShareSubmissionService.cs 622 Full library table scan on every share request - GetItemList() loads every episode in entire library into memory
SkipMe.Db.Plugin/Services/ShareSubmissionService.cs 537 Partial success race condition - If server crashes after HTTP submission but before local recording, segments will be re-submitted infinitely

WARNING

File Line Issue
SkipMe.Db.Plugin/Controllers/ShareController.cs 24 Redundant field assignment with primary constructor
SkipMe.Db.Plugin/Controllers/ShareController.cs 86 Missing exception handling - Unhandled exceptions return raw 500 responses with stack traces
SkipMe.Db.Plugin/Models/ShareSubmitResponse.cs 12 Mutable response DTO properties - Use init; instead of set;
SkipMe.Db.Plugin/Models/SharedUploadFingerprint.cs 18 Value type record missing equality overrides - Used as hash table key with default equality
SkipMe.Db.Plugin/Services/SegmentStore.cs 259 N+1 query anti-pattern - Executes separate SQL query for every candidate fingerprint
SkipMe.Db.Plugin/Services/SegmentStore.cs 317 N+1 query anti-pattern in batch insert - Executes ExecuteNonQueryAsync() individually per fingerprint
SkipMe.Db.Plugin/Services/SegmentStore.cs 359 Insufficient database index - Index only covers ItemId and Segment columns
SkipMe.Db.Plugin/Services/ShareSubmissionService.cs 400 Singleton service depends on scoped service - ShareSubmissionService injects scoped ILibraryManager
SkipMe.Db.Plugin/Services/ShareSubmissionService.cs 769 SQLite connection not disposed async - Only synchronous using is used
SkipMe.Db.Plugin/Services/ShareSubmissionService.cs 1014 DTO includes internal domain properties - Fingerprint property exists on public request types
SkipMe.Db.Plugin/PluginServiceRegistrator.cs 26 Incorrect service lifetime - ShareSubmissionService registered as Singleton

SUGGESTION

File Line Issue
SkipMe.Db.Plugin/Controllers/ShareController.cs 71 Missing [ApiVersion] attribute - Jellyfin plugin controllers require explicit API versioning
SkipMe.Db.Plugin/Models/ShareSubmitRequest.cs 12 Missing nullability attributes - No nullability annotations
SkipMe.Db.Plugin/Models/ShareSubmitRequest.cs 12 No data validation attributes - Missing [Required] / range validation
SkipMe.Db.Plugin/Models/ShareSubmitResponse.cs 14 Missing [JsonPropertyName] attributes - No explicit naming
SkipMe.Db.Plugin/Models/SharedUploadFingerprint.cs 18 Segment should be strongly typed enum
Files Reviewed (8 files)
  • SkipMe.Db.Plugin/Configuration/skipme-index.css
  • SkipMe.Db.Plugin/Configuration/skipme-index.js
  • SkipMe.Db.Plugin/Controllers/ShareController.cs - 1 issue
  • SkipMe.Db.Plugin/Models/ShareSubmitRequest.cs - 3 issues
  • SkipMe.Db.Plugin/Models/ShareSubmitResponse.cs - 2 issues
  • SkipMe.Db.Plugin/Models/SharedUploadFingerprint.cs - 2 issues
  • SkipMe.Db.Plugin/PluginServiceRegistrator.cs - 1 issue
  • SkipMe.Db.Plugin/Services/* - 12 issues

Reviewed by seed-2-0-pro-260328 · 370,685 tokens

@AbandonedCart AbandonedCart merged commit 8a01579 into main Apr 23, 2026
2 checks passed
@AbandonedCart AbandonedCart deleted the copilot/add-sync-and-share-tabs branch April 23, 2026 01:42
Copilot AI added a commit that referenced this pull request Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants