Skip to content

fix(editor): resolve usability and error-prevention bugs#196

Open
rlauuzo wants to merge 24 commits into
masterfrom
capy/segment-editor-usability-fixes
Open

fix(editor): resolve usability and error-prevention bugs#196
rlauuzo wants to merge 24 commits into
masterfrom
capy/segment-editor-usability-fixes

Conversation

@rlauuzo

@rlauuzo rlauuzo commented Jul 2, 2026

Copy link
Copy Markdown
Member

This PR fixes 11 live-verified usability and error-prevention bugs across the segment-editor:

Header & Routing

  • Replace dead useMatchRoute calls with useParams({ strict: false }) so detail/player pages resolve itemId and render correct titles/EpisodeSwitcher

Editor State & Safety

  • Derive isDirty via new areSegmentListsEqual helper (unit tests included)
  • Disable Save when not dirty, show amber-dot "Unsaved changes" indicator
  • Add Discard button to reset local edits
  • Block navigation when dirty via useBlocker with AlertDialog (Cancel/Discard & leave)
  • Wire enableBeforeUnload guard for browser close/reload
  • Confirm segment deletions via AlertDialog (single card + edit dialog instances)
  • Remove duplicate save toast (keep only mutation onSuccess toast)

Consistency & UX

  • Render connecting state for standalone with stored credentials (FilterView)
  • Latch wizard open state so auto wizard shows success step
  • Pass items to SettingsSelect so closed triggers display labels (not raw values)
  • Replace empty text with actionable Empty component (New segment + Paste buttons)
  • Extract shared SegmentTypeMenu (colored dots, t('segmentType.*')) for all new-segment entry points
  • Resolve skip-segment label against current segments prop so Type edits update overlay
  • Remove decorative GripVertical, make actions always visible but de-emphasized

Internationalization

  • Add unsavedChanges/unsavedTitle/unsavedDescription/discardAndLeave/discard keys to en-US, de, fr
  • Add noSegmentsTitle/noSegmentsHint keys for new empty state

Open INTRO-035 INTRO-035

Summary by Sourcery

Improve the segment editor’s safety, routing correctness, and UX consistency across header, player, and filter views.

Bug Fixes:

  • Ensure header detail routes correctly resolve itemId via route params so titles and episode switcher render for all detail/player pages.
  • Prevent false-positive or missed dirty-state detection in the editor by comparing segment lists structurally.
  • Block navigation and browser unload when there are unsaved segment edits, with a confirmation dialog to avoid accidental data loss.
  • Require confirmation before segment deletion from both list cards and edit dialog to avoid accidental removals.
  • Show the correct connecting state in FilterView when running standalone with stored credentials.
  • Make the skip-segment overlay track the current segments so type edits are reflected immediately.
  • Ensure error messages in item lists always render a useful message string instead of an empty fallback.

Enhancements:

  • Add explicit unsaved-changes affordances to the segment editor, including disabling Save when not dirty, an unsaved indicator, and a Discard button to reset edits.
  • Introduce a shared SegmentTypeMenu with colored type dots and translations, and use it for all new-segment entry points in the player and editor empty state.
  • Replace the plain no-segments text with a richer empty state offering New segment and Paste actions.
  • Adjust segment card actions to always remain visible (de-emphasized by default) for clearer discoverability.
  • Keep the onboarding wizard mounted through its success step by latching open state independently of the underlying show flag.
  • Pass options into SettingsSelect so the closed control displays human-readable labels instead of raw values.

Tests:

  • Add unit tests for areSegmentListsEqual to validate segment list dirty-check behavior.
  • Add router-based header tests to verify correct titles and episode switcher behavior on player and series routes.
  • Add tests for FilterView to cover the standalone connecting state with stored credentials.
  • Add tests for SettingsSelect to ensure trigger text reflects the selected option label rather than its raw value.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 2, 2026 04:50
@rlauuzo rlauuzo added the capy Generated by capy.ai label Jul 2, 2026 — with Capy AI
@sourcery-ai

sourcery-ai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR hardens the segment editor and related UI by introducing robust dirty-state tracking with navigation/close guards, confirmation dialogs for destructive actions, a shared SegmentTypeMenu, improved empty/connecting states, and by fixing header routing and labels, with tests added around the new behavior.

Sequence diagram for dirty-state navigation blocking in the segment editor

sequenceDiagram
  actor User
  participant PlayerEditor
  participant segment_utils
  participant useBlocker
  participant AlertDialog
  participant Router

  User->>PlayerEditor: edit segments
  PlayerEditor->>segment_utils: areSegmentListsEqual(localEditingSegments, sortedServerSegments)
  segment_utils-->>PlayerEditor: false (lists differ)
  PlayerEditor->>useBlocker: shouldBlockFn() => isDirty
  useBlocker-->>PlayerEditor: status blocked
  PlayerEditor->>AlertDialog: open unsaved changes dialog

  alt User cancels navigation
    User->>AlertDialog: click Cancel
    AlertDialog->>useBlocker: reset()
    useBlocker-->>Router: navigation cancelled
  else User discards and leaves
    User->>AlertDialog: click Discard & leave
    AlertDialog->>useBlocker: proceed()
    useBlocker-->>Router: continue navigation
  end
Loading

File-Level Changes

Change Details Files
Add robust dirty-state tracking, save/rollback UX, and navigation guards to the segment editor.
  • Derive editor isDirty from a new areSegmentListsEqual helper that compares Id/Type/StartTicks/EndTicks of segment lists.
  • Disable Save when there are no changes, show an amber "Unsaved changes" status, and add a Discard button that resets local edits and active index.
  • Use useBlocker with enableBeforeUnload to block route changes and browser unload when dirty, showing an AlertDialog to cancel or discard and leave.
  • Require confirmation via AlertDialog before deleting a segment card or edit dialog segment, and remove the duplicate manual save toast so only the mutation onSuccess toast remains.
  • Add tests for areSegmentListsEqual’s behavior across length, field, and order differences.
src/components/player/PlayerEditor.tsx
src/lib/segment-utils.ts
src/__tests__/segment-list-equality.test.ts
Unify segment-type creation UX with a shared SegmentTypeMenu and improve segment list empty state.
  • Introduce SegmentTypeMenu as a reusable dropdown listing SEGMENT_TYPES with colored dots and translated labels.
  • Replace bespoke segment-type dropdowns in PlayerControls and PlayerEditor with SegmentTypeMenu, wiring through onSelect, alignment, and portal container as needed.
  • Replace the editor’s plain "no segments" text with an Empty component that offers New segment (via SegmentTypeMenu) and Paste actions.
src/components/segment/SegmentTypeMenu.tsx
src/components/player/PlayerEditor.tsx
src/components/player/PlayerControls.tsx
Fix header detail routing so titles and episode switcher resolve the correct itemId across detail/player routes.
  • Replace brittle useMatchRoute-based itemId detection in Header with useParams({ strict: false }).
  • Remove now-unused DetailRouteMatch type and getMatchedRouteItemId helper.
  • Add integration tests that mount a minimal router and assert correct header titles and EpisodeSwitcher rendering for movie, episode, and series routes.
src/components/Header.tsx
src/__tests__/header-title.test.tsx
Improve FilterView connectivity state handling and error messaging, with tests for the new behavior.
  • Change showConnecting to be true whenever not connected and either in plugin mode or there are stored credentials, so standalone-with-credentials shows a connecting state.
  • Adjust the error message resolution to prefer showError.message when present before falling back to the translated default.
  • Add a test that verifies the connecting state renders for standalone mode with stored credentials.
src/components/filter/FilterView.tsx
src/__tests__/filter-view.test.tsx
Refine segment playback overlay behavior and segment card affordances for better UX consistency.
  • Resolve activeSkipSegment from the current segments prop via rangeById to reflect live edits such as Type changes instead of using a stale cached segment.
  • Remove the decorative GripVertical icon from SegmentSlider and make action buttons always visible (with de-emphasized opacity that increases on hover/focus).
src/components/player/Player.tsx
src/components/segment/SegmentSlider.tsx
Ensure settings selects display human-readable labels in the closed trigger instead of raw values.
  • Pass options as items to the underlying Select in SettingsSelect so that the trigger can render labels for the current value.
  • Add tests that assert the closed trigger shows the selected option label and updates correctly when the value changes.
src/components/settings/primitives/SettingsSelect.tsx
src/__tests__/settings-select.test.tsx
Tidy and extend tests and i18n strings to cover new UX flows.
  • Add unsavedChanges, unsavedTitle, unsavedDescription, discardAndLeave, discard, noSegmentsTitle, and noSegmentsHint keys to en-US, de, and fr locale files for the new dialogs and empty state.
  • Add a test for FilterView’s new connecting state and minor formatting fixes in existing tests (media-item-label, filter-view).
src/i18n/locales/en-US.json
src/i18n/locales/de.json
src/i18n/locales/fr.json
src/__tests__/media-item-label.test.ts
src/__tests__/filter-view.test.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In FilterView, the error message rendering now uses showError.message || … instead of the previous optional chaining; this will throw when showError is undefined, so consider restoring the null-safe access (e.g. showError?.message ?? …) to avoid runtime errors when no error is present.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `FilterView`, the error message rendering now uses `showError.message || …` instead of the previous optional chaining; this will throw when `showError` is undefined, so consider restoring the null-safe access (e.g. `showError?.message ?? …`) to avoid runtime errors when no error is present.

## Individual Comments

### Comment 1
<location path="src/__tests__/header-title.test.tsx" line_range="115-88" />
<code_context>
+  render(<RouterProvider router={router as never} />)
+}
+
+describe('Header on detail routes', () => {
+  afterEach(() => {
+    cleanup()
+  })
+
+  it('renders the movie name as the title on a player route', async () => {
</code_context>
<issue_to_address>
**suggestion (testing):** Add header tests for non-player detail routes and missing/disabled item cases

These tests validate the new `useParams({ strict: false })` behavior for `/player/$itemId` and `/series/$itemId`. To more fully cover the routing/title logic and guard against regressions, please also add:

- A case where `itemId` is absent (e.g. `/`) to ensure the header renders a safe default and doesn’t crash.
- A case where `useItem` is disabled or the item is missing from `itemsById`, verifying the title and EpisodeSwitcher fail gracefully.
- If other detail routes also rely on `itemId` (e.g. `/album/$itemId`, `/artist/$itemId`), a test for at least one of those to confirm parity with the old `useMatchRoute` behavior.

Suggested implementation:

```typescript
  it('renders the episode switcher with the episode label on a player route', async () => {
    renderHeaderAt('/player/episode-1')

    const switcher = await screen.findByTestId('episode-switcher')
    expect(switcher).toBeInTheDocument()
  })

  it('renders a safe default title when no itemId param is present', async () => {
    renderHeaderAt('/')

    // When there is no detail route param, the header should still render
    const heading = await screen.findByRole('heading', { level: 1 })
    expect(heading).toBeInTheDocument()
  })

  it('falls back gracefully when the item is missing from itemsById', async () => {
    // Simulate a detail route with an itemId that does not exist
    renderHeaderAt('/player/nonexistent-item-id')

    const heading = await screen.findByRole('heading', { level: 1 })
    expect(heading).toBeInTheDocument()

    // EpisodeSwitcher should not render when there is no valid episode/item
    const switcher = screen.queryByTestId('episode-switcher')
    expect(switcher).toBeNull()
  })

  it('renders a title on a series detail route', async () => {
    // Series detail routes also rely on itemId; verify parity with player routes
    renderHeaderAt('/series/episode-1')

    const heading = await screen.findByRole('heading', { level: 1 })
    expect(heading).toBeInTheDocument()
  })

```

If your header implementation renders a specific fallback title when no `itemId` is present or when the item is missing (e.g. `"Discover"` or `"Unknown item"`), you can strengthen these tests by asserting `heading.textContent` equals that string instead of only checking `toBeInTheDocument()`.  
Similarly, if your series detail route uses a known fixture (e.g. `/series/series-1` with a specific title), update the path and expectation in the `"renders a title on a series detail route"` test to match those real values.  
If there are additional detail routes such as `/album/$itemId` or `/artist/$itemId`, you can duplicate the last test and adjust the route and expectations to cover them as well for full parity with the old `useMatchRoute` behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

<Outlet />
</>
),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add header tests for non-player detail routes and missing/disabled item cases

These tests validate the new useParams({ strict: false }) behavior for /player/$itemId and /series/$itemId. To more fully cover the routing/title logic and guard against regressions, please also add:

  • A case where itemId is absent (e.g. /) to ensure the header renders a safe default and doesn’t crash.
  • A case where useItem is disabled or the item is missing from itemsById, verifying the title and EpisodeSwitcher fail gracefully.
  • If other detail routes also rely on itemId (e.g. /album/$itemId, /artist/$itemId), a test for at least one of those to confirm parity with the old useMatchRoute behavior.

Suggested implementation:

  it('renders the episode switcher with the episode label on a player route', async () => {
    renderHeaderAt('/player/episode-1')

    const switcher = await screen.findByTestId('episode-switcher')
    expect(switcher).toBeInTheDocument()
  })

  it('renders a safe default title when no itemId param is present', async () => {
    renderHeaderAt('/')

    // When there is no detail route param, the header should still render
    const heading = await screen.findByRole('heading', { level: 1 })
    expect(heading).toBeInTheDocument()
  })

  it('falls back gracefully when the item is missing from itemsById', async () => {
    // Simulate a detail route with an itemId that does not exist
    renderHeaderAt('/player/nonexistent-item-id')

    const heading = await screen.findByRole('heading', { level: 1 })
    expect(heading).toBeInTheDocument()

    // EpisodeSwitcher should not render when there is no valid episode/item
    const switcher = screen.queryByTestId('episode-switcher')
    expect(switcher).toBeNull()
  })

  it('renders a title on a series detail route', async () => {
    // Series detail routes also rely on itemId; verify parity with player routes
    renderHeaderAt('/series/episode-1')

    const heading = await screen.findByRole('heading', { level: 1 })
    expect(heading).toBeInTheDocument()
  })

If your header implementation renders a specific fallback title when no itemId is present or when the item is missing (e.g. "Discover" or "Unknown item"), you can strengthen these tests by asserting heading.textContent equals that string instead of only checking toBeInTheDocument().
Similarly, if your series detail route uses a known fixture (e.g. /series/series-1 with a specific title), update the path and expectation in the "renders a title on a series detail route" test to match those real values.
If there are additional detail routes such as /album/$itemId or /artist/$itemId, you can duplicate the last test and adjust the route and expectations to cover them as well for full parity with the old useMatchRoute behavior.

Copilot AI left a comment

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.

Pull request overview

This PR improves the segment-editor’s safety and usability by tightening dirty-state detection, adding user confirmations/guards to prevent accidental data loss, and consolidating segment-creation UI while fixing a few routing/UX inconsistencies.

Changes:

  • Add editor safety features (structural dirty check, disable Save when clean, discard/reset actions, navigation/unload blocking, deletion confirmations).
  • Consolidate/standardize segment creation UI via a shared SegmentTypeMenu and improve empty-state/actions visibility.
  • Fix supporting UX/routing behaviors (header param resolution, FilterView connecting/error messaging, SettingsSelect trigger labels) and add tests for key regressions.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/routes/-root-components.tsx Latches wizard open state so it remains mounted through success.
src/lib/segment-utils.ts Adds areSegmentListsEqual helper for dirty-state detection.
src/i18n/locales/en-US.json Adds editor empty/unsaved strings and common.cancel.
src/i18n/locales/de.json Adds editor empty/unsaved strings and common.cancel.
src/i18n/locales/fr.json Adds editor empty/unsaved strings and common.cancel.
src/components/settings/primitives/SettingsSelect.tsx Passes items/options so closed select trigger can show labels.
src/components/segment/SegmentTypeMenu.tsx New shared dropdown for selecting segment type when creating segments.
src/components/segment/SegmentSlider.tsx Removes decorative grip; keeps actions visible (de-emphasized) until hover/focus.
src/components/player/PlayerEditor.tsx Implements dirty state, save/discard affordances, navigation blocking, delete confirm, richer empty state, and uses SegmentTypeMenu.
src/components/player/PlayerControls.tsx Uses shared SegmentTypeMenu for segment creation control.
src/components/player/Player.tsx Resolves skip overlay segment against current segments so edits reflect immediately.
src/components/Header.tsx Uses useParams({ strict: false }) to resolve itemId across detail/player routes.
src/components/filter/FilterView.tsx Shows connecting state in standalone-with-credentials and improves error message fallback.
src/tests/settings-select.test.tsx Tests SettingsSelect shows selected option label (not raw value).
src/tests/segment-list-equality.test.ts Unit tests for areSegmentListsEqual.
src/tests/header-title.test.tsx Router-based header title/episode switcher tests for detail routes.
src/tests/filter-view.test.tsx Adds coverage for standalone connecting state with stored credentials.
src/tests/media-item-label.test.ts Formatting/refactor only; no functional change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/components/player/PlayerEditor.tsx Outdated
Comment on lines +752 to +756
<AlertDialogAction
variant="destructive"
onClick={() => blocker.proceed?.()}
>
{t('editor.discardAndLeave', 'Discard & leave')}
Comment thread src/lib/segment-utils.ts
Comment on lines +18 to +31
export const areSegmentListsEqual = (
a: ReadonlyArray<MediaSegmentDto>,
b: ReadonlyArray<MediaSegmentDto>,
): boolean =>
a.length === b.length &&
a.every((segment, index) => {
const other = b[index]
return (
segment.Id === other.Id &&
segment.Type === other.Type &&
segment.StartTicks === other.StartTicks &&
segment.EndTicks === other.EndTicks
)
})
Comment thread src/components/player/PlayerEditor.tsx Outdated
Comment on lines +344 to +346
const handleConfirmDeleteSegment = () => {
if (pendingDeleteIndex !== null) handleDeleteSegment(pendingDeleteIndex)
setPendingDeleteIndex(null)
@kilo-code-bot

kilo-code-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The incremental change adds a recovery mechanism in useVibrantColor to re-kick color extraction when colors are needed but absent, handling edge cases where extraction was missed after enabled flips back (e.g., monochrome toggled off) or cache entries were evicted while mounted. The implementation uses useEffect with correct dependency tracking and the existing caches/pending-extraction deduplication make this efficient.

Files Reviewed (1 file)
  • src/hooks/use-vibrant-color.ts - Recovery kick effect for missed extractions

rlauuzo and others added 4 commits July 2, 2026 05:00
… title tests

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…it tests

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…te on item change, delete by segment id, harden areSegmentListsEqual

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 2a89473.

rlauuzo and others added 14 commits July 3, 2026 05:37
…izard latch, deterministic list keys

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…explicit key cannot be overwritten

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ize media card surfaces, and re (#197)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…s are full-bleed

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* Contain vibrant color usage to accent layers in detail views, neutralize media card surfaces, and re

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep overlay rounding embedded-only so fullscreen error/loading states are full-bleed

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Address review: drop color-mix from AmbientGradient, cover windowed vs fullscreen surface styling in tests

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…-insensitive segment equality, dead-end connecting state for failed stored credentials

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…h save finish since aborting between its delete and recreate phases would wipe remote segments

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ch to one state machine, inline validation-state indirection, drop unused preload export

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
… so the key cannot be overwritten

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…rtial credential matches as non-mock in dev auto-login

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
… disabling monochrome always restores artwork colors

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…k-seeded SchemeContent tokens drive existing shadcn theme vars on detail pages

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
</div>
<p className="text-destructive text-center text-lg">
{showError.message}
{showError.message ||

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: Runtime error - showError.message accessed without null check

showError is derived from collectionsError || itemsError and can be undefined. Accessing .message on undefined will throw at runtime when no error is present.

Suggested change
{showError.message ||
{showError?.message ||

@kilo-code-bot

kilo-code-bot Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

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

Resolved Issues

File Line Resolution
src/components/filter/FilterView.tsx 377 CRITICAL issue addressed: PR added `
Files Reviewed (9 files)
  • src/components/filter/FilterView.tsx - Error handling improved with fallback
  • src/components/player/Player.tsx - Removed vibrantColors prop, changes look correct
  • src/components/player/PlayerControls.tsx - Simplified with SegmentTypeMenu, changes look correct
  • src/components/player/PlayerEditor.tsx - Added navigation blocking, dirty state tracking, changes look correct
  • src/lib/segment-utils.ts - Added areSegmentListsEqual and resolveSegmentIndex, changes look correct
  • src/components/filter/MediaCard.tsx - Simplified with getSeriesCountLabel, changes look correct
  • src/components/filter/media-item-label.ts - Added getSeriesCountLabel function, changes look correct
  • src/services/items/api.ts - Added ChildCount/RecursiveItemCount fields, changes look correct
  • src/styles.css - Radius changed from 0.75rem to 0.5rem, CSS cleanup, changes look intentional
  • tools/server.mjs - Added ChildCount/RecursiveItemCount for series, changes look correct

Fix these issues in Kilo Cloud

Previous Review Summaries (2 snapshots, latest commit f6621d0)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit f6621d0)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (13 files)
  • src/components/filter/FilterView.tsx - No issues (previous CRITICAL issue resolved)
  • src/components/player/Player.tsx - No issues
  • src/components/player/PlayerEditor.tsx - No issues (previous issues resolved)
  • src/components/player/PlayerScrubber.tsx - No issues
  • src/components/routes/PlayerItemRoute.tsx - No issues
  • src/components/routes/SeriesItemRoute.tsx - No issues
  • src/components/ui/dynamic-theme-scope.tsx - No issues
  • src/hooks/use-artwork-color.ts - No issues (new file)
  • src/hooks/use-vibrant-color.ts - No issues (deleted file)
  • src/lib/cache-manager.ts - No issues
  • src/lib/segment-utils.ts - No issues (previous issue resolved)
  • src/__tests__/filter-view.test.tsx - No issues
  • src/__tests__/header-title.test.tsx - No issues

Changes Summary

This PR successfully refactors the color extraction system from node-vibrant (Web Worker-based) to @material/material-color-utilities (Material You pipeline), simplifying the architecture by:

  • Replacing useVibrantColor hook with useArtworkColor that extracts a single seed color
  • Simplifying DynamicThemeScope to accept seedColor instead of full color palette
  • Removing vibrantColors prop threading through Player, PlayerEditor, and PlayerScrubber components
  • Using CSS variables and theme tokens instead of inline color styles

All previous review issues have been resolved:

  • ✅ FilterView.tsx:377 - showError.message access is properly guarded
  • ✅ PlayerEditor.tsx - Unsaved-changes dialog now discards edits before proceeding
  • ✅ PlayerEditor.tsx - Segment deletion now tracks by Id with resolveSegmentIndex
  • ✅ segment-utils.ts - areSegmentListsEqual now handles undefined elements safely

Previous review (commit 2a89473)

Status: 1 Critical Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
src/components/filter/FilterView.tsx 377 showError.message accessed without optional chaining; showError can be undefined when both collectionsError and itemsError are undefined, causing a runtime error
Files Reviewed (17 files)
  • src/components/filter/FilterView.tsx - 1 critical issue
  • src/lib/dev-mock-server-login.ts - Fixed (changed && to ||)
  • src/lib/segment-utils.ts - Fixed (handles undefined elements)
  • src/components/player/PlayerEditor.tsx - Fixed (discards edits before proceeding)
  • src/components/player/Player.tsx - Fixed (resolves skip segment by Id)
  • src/components/Header.tsx - Changed to use useParams({ strict: false })
  • src/routes/-root-components.tsx - Fixed (wizard state latch)
  • src/components/segment/SegmentTypeMenu.tsx - New component
  • src/components/ui/dynamic-theme-scope.tsx - New component
  • src/lib/m3-dynamic-theme.ts - New component
  • src/i18n/locales/en-US.json - New translations
  • src/i18n/locales/de.json - New translations
  • src/i18n/locales/fr.json - New translations
  • src/components/settings/primitives/SettingsSelect.tsx - Fixed (shows labels)
  • src/__tests__/header-title.test.tsx - New tests
  • src/__tests__/segment-list-equality.test.ts - New tests
  • src/__tests__/settings-select.test.tsx - New tests
  • src/__tests__/filter-view.test.tsx - New tests
  • src/__tests__/use-connection-init.test.tsx - New tests
  • src/__tests__/dev-mock-server-login.test.ts - New tests

Fix these issues in Kilo Cloud
EOF


Reviewed by laguna-xs-2.1-20260625 · Input: 324.9K · Output: 26.6K · Cached: 10.8M

…tion

The M3 dynamic scheme only needs a single seed color, so the node-vibrant
palette pipeline and culori color math are redundant. useArtworkColor
downsamples artwork on a shared canvas and quantizes it with MCU's
QuantizerCelebi + Score (the Material You source-color pipeline), feeding
DynamicThemeScope a seed hex directly. Removes node-vibrant, culori, and
@types/culori.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ine titles, season/episode counts

Drop the global radius scale one tier (--radius 12px -> 8px, with 2xl-4xl
overrides derived from it) so every surface sharpens consistently. Media
card titles truncate to a single line, and series cards show the season
count next to the year (episode count for single-season series). The
library browse query now requests ChildCount/RecursiveItemCount, and the
mock server exposes matching fixture counts.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
</div>
<p className="text-destructive text-center text-lg">
{showError.message}
{showError.message ||

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: Use optional chaining for safer property access

Using showError?.message would be more robust than showError.message since it handles the case where showError might not have a message property. The || fallback already handles undefined values, but optional chaining provides an extra layer of safety and makes the intent clearer.

Suggested change
{showError.message ||
{showError?.message ||
t('items.loadError', {
defaultValue: 'Unable to load media items',
})}

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Files Reviewed (3 files)
  • package.json - Dependency version bumps
  • pnpm-lock.yaml - Lock file updates
  • pnpm-workspace.yaml - Added lfa-ponyfill to allowBuilds
Previous Review Summaries (5 snapshots, latest commit f0acb80)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit f0acb80)

Status: No Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Files Reviewed (2 files)
  • src/styles.css - Theme color changes from oklch() to hex values via M3 pipeline, no issues
  • tools/generate-base-theme.mjs - New theme generation tool, no issues

Fix these issues in Kilo Cloud

Previous review (commit 1574ee2)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (10 files)
  • src/components/player/Player.tsx - Removed vibrantColors prop, changes look correct
  • src/components/player/PlayerEditor.tsx - Navigation blocking and dirty state tracking, changes look correct
  • src/components/player/PlayerScrubber.tsx - Removed vibrantColors prop, changes look correct
  • src/components/player/PlayerControls.tsx - Simplified with SegmentTypeMenu, changes look correct
  • src/components/routes/PlayerItemRoute.tsx - Uses useArtworkColor, changes look correct
  • src/components/routes/SeriesItemRoute.tsx - Uses useArtworkColor, changes look correct
  • src/components/ui/dynamic-theme-scope.tsx - Accepts seedColor, changes look correct
  • src/hooks/use-artwork-color.ts - New hook for seed color extraction, changes look correct
  • src/components/filter/MediaCard.tsx - Uses getSeriesCountLabel, changes look correct
  • src/components/filter/media-item-label.ts - Added getSeriesCountLabel, changes look correct

Fix these issues in Kilo Cloud

Previous review (commit d86daf3)

Status: No Issues Found | Recommendation: Merge

Overview

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

Resolved Issues

File Line Resolution
src/components/filter/FilterView.tsx 377 CRITICAL issue addressed: PR added `
Files Reviewed (9 files)
  • src/components/filter/FilterView.tsx - Error handling improved with fallback
  • src/components/player/Player.tsx - Removed vibrantColors prop, changes look correct
  • src/components/player/PlayerControls.tsx - Simplified with SegmentTypeMenu, changes look correct
  • src/components/player/PlayerEditor.tsx - Added navigation blocking, dirty state tracking, changes look correct
  • src/lib/segment-utils.ts - Added areSegmentListsEqual and resolveSegmentIndex, changes look correct
  • src/components/filter/MediaCard.tsx - Simplified with getSeriesCountLabel, changes look correct
  • src/components/filter/media-item-label.ts - Added getSeriesCountLabel function, changes look correct
  • src/services/items/api.ts - Added ChildCount/RecursiveItemCount fields, changes look correct
  • src/styles.css - Radius changed from 0.75rem to 0.5rem, CSS cleanup, changes look intentional
  • tools/server.mjs - Added ChildCount/RecursiveItemCount for series, changes look correct

Fix these issues in Kilo Cloud

Previous review (commit f6621d0)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (13 files)
  • src/components/filter/FilterView.tsx - No issues (previous CRITICAL issue resolved)
  • src/components/player/Player.tsx - No issues
  • src/components/player/PlayerEditor.tsx - No issues (previous issues resolved)
  • src/components/player/PlayerScrubber.tsx - No issues
  • src/components/routes/PlayerItemRoute.tsx - No issues
  • src/components/routes/SeriesItemRoute.tsx - No issues
  • src/components/ui/dynamic-theme-scope.tsx - No issues
  • src/hooks/use-artwork-color.ts - No issues (new file)
  • src/hooks/use-vibrant-color.ts - No issues (deleted file)
  • src/lib/cache-manager.ts - No issues
  • src/lib/segment-utils.ts - No issues (previous issue resolved)
  • src/__tests__/filter-view.test.tsx - No issues
  • src/__tests__/header-title.test.tsx - No issues

Changes Summary

This PR successfully refactors the color extraction system from node-vibrant (Web Worker-based) to @material/material-color-utilities (Material You pipeline), simplifying the architecture by:

  • Replacing useVibrantColor hook with useArtworkColor that extracts a single seed color
  • Simplifying DynamicThemeScope to accept seedColor instead of full color palette
  • Removing vibrantColors prop threading through Player, PlayerEditor, and PlayerScrubber components
  • Using CSS variables and theme tokens instead of inline color styles

All previous review issues have been resolved:

  • ✅ FilterView.tsx:377 - showError.message access is properly guarded
  • ✅ PlayerEditor.tsx - Unsaved-changes dialog now discards edits before proceeding
  • ✅ PlayerEditor.tsx - Segment deletion now tracks by Id with resolveSegmentIndex
  • ✅ segment-utils.ts - areSegmentListsEqual now handles undefined elements safely

Previous review (commit 2a89473)

Status: 1 Critical Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
src/components/filter/FilterView.tsx 377 showError.message accessed without optional chaining; showError can be undefined when both collectionsError and itemsError are undefined, causing a runtime error
Files Reviewed (17 files)
  • src/components/filter/FilterView.tsx - 1 critical issue
  • src/lib/dev-mock-server-login.ts - Fixed (changed && to ||)
  • src/lib/segment-utils.ts - Fixed (handles undefined elements)
  • src/components/player/PlayerEditor.tsx - Fixed (discards edits before proceeding)
  • src/components/player/Player.tsx - Fixed (resolves skip segment by Id)
  • src/components/Header.tsx - Changed to use useParams({ strict: false })
  • src/routes/-root-components.tsx - Fixed (wizard state latch)
  • src/components/segment/SegmentTypeMenu.tsx - New component
  • src/components/ui/dynamic-theme-scope.tsx - New component
  • src/lib/m3-dynamic-theme.ts - New component
  • src/i18n/locales/en-US.json - New translations
  • src/i18n/locales/de.json - New translations
  • src/i18n/locales/fr.json - New translations
  • src/components/settings/primitives/SettingsSelect.tsx - Fixed (shows labels)
  • src/__tests__/header-title.test.tsx - New tests
  • src/__tests__/segment-list-equality.test.ts - New tests
  • src/__tests__/settings-select.test.tsx - New tests
  • src/__tests__/filter-view.test.tsx - New tests
  • src/__tests__/use-connection-init.test.tsx - New tests
  • src/__tests__/dev-mock-server-login.test.ts - New tests

Fix these issues in Kilo Cloud
EOF


Reviewed by laguna-xs-2.1-20260625 · Input: 419.9K · Output: 18.5K · Cached: 3.4M

rlauuzo and others added 3 commits July 5, 2026 08:05
The header already resolves the current detail item for its title, so it
derives the same artwork seed as the page below it (shared caches make
this free) and wraps its chrome in DynamicThemeScope. Back/home/settings
buttons pick up secondary-container tones with proper on-container icon
color, and the header surface takes the scheme's tonal tint instead of
staying neutral over a themed page.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
The base tokens were hand-picked oklch values (grey surfaces, orange
primary, teal secondary) unrelated to the artwork-derived dynamic schemes.
tools/generate-base-theme.mjs now feeds the existing brand orange through
the same SchemeContent pipeline as m3-dynamic-theme.ts and emits the full
light/dark token set, so the whole app shares one M3 tonal system: warm
tinted surfaces, proper container/on-container pairs, and a real error
color in dark mode (destructive was teal). Monochrome mode now also
neutralizes the tinted surface tokens so it stays truly grey.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capy Generated by capy.ai

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants