Kareadita/Kavita · warning · KavitaException

dashboard-stream-only-delete-smart-filter

Error message

dashboard-stream-only-delete-smart-filter

What it means

Thrown by StreamService.DeleteDashboardSmartFilterStream when the loaded stream's StreamType is not DashboardStreamType.SmartFilter. Only user-created SmartFilter dashboard entries are deletable here; built-in dashboard widgets must be hidden via update-dashboard-stream, not deleted. It is a localized KavitaException surfaced as HTTP 500.

Source

Thrown at Kavita.Services/StreamService.cs:390

        catch (Exception ex)
        {
            logger.LogError(ex, "There was an exception deleting SideNav Smart Filter Stream: {FilterId}", sideNavStreamId);
            throw;
        }
    }

    public async Task DeleteDashboardSmartFilterStream(int userId, int dashboardStreamId, CancellationToken ct = default)
    {
        try
        {
            var stream = await unitOfWork.UserRepository.GetDashboardStream(dashboardStreamId, ct);
            if (stream == null) throw new KavitaException("dashboard-stream-doesnt-exist");

            if (stream.AppUserId != userId) throw new KavitaException("dashboard-stream-doesnt-exist");

            if (stream.StreamType != DashboardStreamType.SmartFilter)
            {
                throw new KavitaException("dashboard-stream-only-delete-smart-filter");
            }

            unitOfWork.UserRepository.Delete(stream);

            await unitOfWork.CommitAsync(ct);
        } catch (Exception ex)
        {
            logger.LogError(ex, "There was an exception deleting Dashboard Smart Filter Stream: {FilterId}", dashboardStreamId);
            throw;
        }
    }

    public async Task RenameSmartFilterStreams(AppUserSmartFilter smartFilter, CancellationToken ct = default)
    {
        var sideNavStreams = await unitOfWork.UserRepository.GetSideNavStreamWithFilter(smartFilter.Id, ct);
        var dashboardStreams = await unitOfWork.UserRepository.GetDashboardStreamWithFilter(smartFilter.Id, ct);

        foreach (var sideNavStream in sideNavStreams)

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Gate the delete UI on streamType === 'smartFilter'; hide delete for built-in dashboard widgets.
  2. To remove a built-in widget from the dashboard, POST update-dashboard-stream with visible=false instead.
  3. If a smart-filter must be removed, confirm its type before showing the delete control.

Example fix

// before
canDelete(s: DashboardStreamDto) { return true; }
// after
canDelete(s: DashboardStreamDto) { return s.streamType === 'smartFilter'; }
Defensive patterns

Strategy: type-guard

Validate before calling

function isSmartFilterDashboardStream(s: DashboardStreamDto): boolean {
  return s?.streamType === 'smartFilter';
}

Type guard

function isDeletableDashboardStream(s: DashboardStreamDto): s is DashboardStreamDto {
  return s?.streamType === 'smartFilter';
}

Try / catch

on 'dashboard-stream-only-delete-smart-filter', hide the delete control and offer update-dashboard-stream (visible=false) instead

Prevention

When it happens

Trigger: DELETE /api/streams/smart-filter-dashboard-stream?dashboardStreamId=N where the stream exists and is owned by the user but its StreamType is not SmartFilter (e.g., a built-in dashboard widget).

Common situations: The UI offers delete on a built-in dashboard stream; the user expects to remove a system widget through the delete endpoint instead of hiding it.

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/255590a9faf3099a. Report an issue: GitHub.