Skip to content

refactor(ui): replace SVG artwork placeholders with CSS #292

Merged
imajes merged 3 commits into
grimmory-tools:developfrom
alexhb1:perf-2-icon-placeholder
Mar 31, 2026
Merged

refactor(ui): replace SVG artwork placeholders with CSS #292
imajes merged 3 commits into
grimmory-tools:developfrom
alexhb1:perf-2-icon-placeholder

Conversation

@alexhb1

@alexhb1 alexhb1 commented Mar 30, 2026

Copy link
Copy Markdown
Member

Description

Currently, books with no artwork use a generated SVG with specific (ugly) styling and has a complex and intensive pipeline for displaying them, especially with large libraries. They're generated on the fly with no storage or caching. This replaces the entire approach with a simplified in-line placeholder div with a bit of basic text and CSS.

Before vs after:
Screenshot 2026-03-30 at 23 35 09 Screenshot 2026-03-30 at 23 40 26

Changes

  • Removes all SVG generating code and plumbing for displaying placeholder images
  • Adds a basic CSS placeholder with title+author and background color

Will mark as ready once my other layout shell rework PR is done and through, this can be sorted afterwards.

Summary by CodeRabbit

  • New Features

    • Introduced a reusable cover placeholder that displays title/author when cover images are missing.
  • Bug Fixes

    • Replaced broken/empty image fallbacks with placeholders across book lists, cards, tables, series, metadata views, notebooks, and reader sidebars.
    • Thumbnail/cover APIs now consistently indicate absent covers, avoiding invalid image sources.
  • Tests

    • Updated tests to validate placeholder behavior and new cover URL semantics.

@coderabbitai

coderabbitai Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d38d127d-a5dc-4782-96dd-a7826f8c037f

📥 Commits

Reviewing files that changed from the base of the PR and between 0d8d0b8 and d3f2f96.

📒 Files selected for processing (6)
  • frontend/src/app/features/book/components/book-browser/book-card/book-card.component.html
  • frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.html
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.html
  • frontend/src/app/features/notebook/components/notebook/notebook.component.ts
  • frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.html
  • frontend/src/app/shared/service/url-helper.service.ts
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/app/features/book/components/book-browser/book-card/book-card.component.html
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/app/features/notebook/components/notebook/notebook.component.ts
  • frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.html
  • frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.html
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/src/**/*.{ts,tsx,html,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation in TypeScript, HTML, and SCSS files

Files:

  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.html
  • frontend/src/app/shared/service/url-helper.service.ts
frontend/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/app/**/*.{ts,tsx}: Prefer inject() over constructor injection
Follow frontend/eslint.config.js: component selectors use app-*, directive selectors use app*, and any is disallowed

Files:

  • frontend/src/app/shared/service/url-helper.service.ts
🔇 Additional comments (4)
frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.html (1)

8-18: Well-implemented conditional cover rendering.

The pattern correctly evaluates getBookCoverUrl(book) once and conditionally renders either the image or placeholder. The alt text improvement from the past review is properly addressed.

Minor suggestion: Line 17 is quite long and uses inline styles. Consider breaking it into multiple lines and moving the sizing to a CSS class for consistency with how width="250" is handled on <p-image>.

♻️ Optional formatting improvement
           } `@else` {
-            <app-cover-placeholder style="width: 250px; height: 350px;" [title]="book.metadata?.title ?? ''" [author]="(book.metadata?.authors ?? []).join(', ')" size="lg" />
+            <app-cover-placeholder
+              class="cover-placeholder"
+              [title]="book.metadata?.title ?? ''"
+              [author]="(book.metadata?.authors ?? []).join(', ')"
+              size="lg" />
           }

Then in the component's SCSS:

.cover-placeholder {
  width: 250px;
  height: 350px;
}

[approve_code_changes, suggest_optional_refactor]

frontend/src/app/shared/service/url-helper.service.ts (3)

1-14: LGTM - Service setup and dependency injection follow best practices.

The service correctly uses inject() for dependency injection and the overall structure is clean. The removal of SVG generation dependencies (BookService, CoverGeneratorComponent) simplifies the service appropriately.


30-36: Good design: "Direct" variants provide fallback URL behavior.

The getDirectThumbnailUrl and getDirectAudiobookThumbnailUrl methods correctly maintain their string return type, allowing callers to get a URL even without the *UpdatedOn cache-busting parameter. This provides a sensible fallback mechanism for use cases that require a URL regardless of cover presence.

Also applies to: 58-64


25-28: No issues found — all callers properly handle the nullable return types.

The change from string to string | null is correctly implemented:

  • BookMetadata.thumbnailUrl is typed as string | null (book.model.ts, line 51), so assignments in metadata-picker.component.ts (lines 78, 182) are type-safe.
  • audiobookThumbnailUrl is a form-only field and does not map to BookMetadata, so there is no type mismatch.
  • All callers throughout the codebase (sidebar services, components, etc.) properly assign nullable return values to properties typed as string | null or use them in methods with nullable return types.

📝 Walkthrough

Walkthrough

Replaces the old SVG-based cover generator with a reusable CoverPlaceholderComponent, makes URL helper methods return nullable URLs (no autogenerated fallbacks), and updates many templates/types to conditionally render placeholders when cover URLs are absent.

Changes

Cohort / File(s) Summary
Core Cover Component
frontend/src/app/shared/components/cover-generator/cover-generator.component.ts, frontend/src/app/shared/components/cover-generator/cover-generator.component.spec.ts
Replaced SVG/data-URL generator with CoverPlaceholderComponent. Removed SVG-generation logic and helpers; added coverColorFor() and CoverPlaceholderSize API. Tests updated from SVG assertions to DOM and color-function checks.
URL Helper Service
frontend/src/app/shared/service/url-helper.service.ts, frontend/src/app/shared/service/url-helper.service.spec.ts
Removed fallback cover generation; methods getThumbnailUrl/getCoverUrl/getAudiobookCoverUrl/getAudiobookThumbnailUrl now return `string
Book browser & card variants
frontend/src/app/features/book/components/book-browser/book-card/book-card.component.html, .../book-card.component.ts, .../book-table/book-table.component.html, .../book-table/book-table.component.ts, .../book-card-lite/...
Added CoverPlaceholderComponent imports; templates switched to @if(...; as coverUrl) / @else`` rendering to show <img> when URL present or `app-cover-placeholder` when absent. Thumbnail getter types made nullable.
Book searcher & duplicate merger
frontend/src/app/features/book/components/book-searcher/..., .../duplicate-merger/...
Imported placeholder component and changed templates to conditional thumbnail-or-placeholder rendering; no other logic changes.
Series pages & cards
frontend/src/app/features/series-browser/components/series-card/..., frontend/src/app/features/book/components/series-page/...
Added placeholder import, made thumbnail/getCoverUrl return types nullable, updated templates to render placeholders (multiple sizes) when URLs are missing; unit test expectation updated to expect null.
Notebook
frontend/src/app/features/notebook/components/notebook/notebook.component.html, .../notebook.component.ts
BookGroup.thumbnailUrl made nullable, added placeholder import, template now falls back to placeholder when thumbnailUrl is absent. Audiobook direct thumbnail selector adjusted.
Metadata editor/viewer & tabs
frontend/src/app/features/metadata/.../metadata-editor.component.*, .../metadata-viewer.component.*, .../metadata-tabs.component.*
Added placeholder imports, changed getBookCoverUrl() return type to `string
Readers — ebook & CBX sidebars, dialogs
frontend/src/app/features/readers/ebook-reader/layout/sidebar/..., .../dialogs/metadata-dialog.*, frontend/src/app/features/readers/cbx-reader/layout/sidebar/...
Replaced inline placeholder markup/styles with app-cover-placeholder; added placeholder to standalone imports; templates conditionally render placeholder and wire existing interactions (click/keyboard).
Series/stacked covers
frontend/src/app/features/series-browser/components/series-card/...
Stacked cover loop now uses cached coverUrl and falls back to placeholder when absent; getCoverUrl() returns `string
Shared tests & helpers
frontend/src/app/shared/components/cover-generator/..., frontend/src/app/shared/service/url-helper.service.spec.ts
Tests adjusted: removed reliance on generated SVG/data-URL behavior; added DOM-based assertions for CoverPlaceholderComponent and tests for coverColorFor. URL helper tests expect null when update markers missing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • balazs-szucs

Poem

🐰 I hopped through templates, tidy and spry,

when covers go missing, a color will try.
No base64 shadows, just hues in a row,
placeholders blooming where images won't show.
The rabbit nods softly — the UI can glow.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows the conventional commit format with 'refactor' type prefix and a clear summary of the main change.
Description check ✅ Passed The PR description includes a clear Description section explaining the rationale and visual before/after examples, but is missing the 'Linked Issue' section from the template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Comment @coderabbitai help to get the list of available commands and usage tips.

@balazs-szucs

Copy link
Copy Markdown
Contributor

Currently, books with no artwork use a generated SVG with specific (ugly) styling and has a complex and intensive pipeline for displaying them, especially with large libraries

🤣

@alexhb1 alexhb1 marked this pull request as ready for review March 31, 2026 17:28

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.html (1)

17-18: Make placeholder cover interaction consistent with image cover

When coverUrl is missing, the fallback cover is no longer clickable, while the image cover opens metadata. Consider giving the placeholder the same click/keyboard behavior for parity.

Proposed change
-        } `@else` {
-          <app-cover-placeholder class="book-cover" [title]="bookInfo().title" [author]="bookInfo().authors" size="sm" />
+        } `@else` {
+          <button
+            type="button"
+            class="book-cover"
+            (click)="onCoverClick()"
+            (keydown.enter)="onCoverClick()"
+            aria-label="Open book metadata">
+            <app-cover-placeholder [title]="bookInfo().title" [author]="bookInfo().authors" size="sm" />
+          </button>
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.html`
around lines 17 - 18, The placeholder app-cover-placeholder should have the same
click and keyboard behavior as the image cover: add the same click handler used
by the image cover (e.g., (click)="openMetadata(bookInfo())") and make it
keyboard-accessible by adding tabindex="0" and keydown handlers for Enter/Space
that call the same openMetadata method; also add role="button" and an
appropriate aria-label (e.g., aria-label="Open book metadata for
{{bookInfo().title}}") so the fallback cover (when coverUrl is missing) behaves
identically to the image cover.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@frontend/src/app/features/book/components/book-browser/book-card/book-card.component.html`:
- Around line 17-19: The fallback branch for missing covers uses
<app-cover-placeholder> but omits the same CSS classes used by the image branch,
breaking sizing/aspect styles; update the <app-cover-placeholder> element in
book-card.component.html to include the same class names and state bindings as
the image branch (e.g., add the "book-cover" class and the "square-cover" state
class/binding used there) so the placeholder and image render with identical
layout/styling behavior.

In
`@frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.html`:
- Around line 8-10: The placeholder branch in book-card-lite-component.html is
missing the book-cover CSS class used by the real image branch; update the
<app-cover-placeholder> element (used in the else branch of the card rendering)
to include the same "book-cover" class so its styling and layout match the <img>
branch (keep existing inputs title and author intact).

In
`@frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.html`:
- Around line 9-12: The cover image currently uses a generic alt="Image"; update
the <p-image> in metadata-viewer.component.html to use a meaningful alt or an
empty decorative alt by binding alt to a component property (e.g.,
[alt]="coverAlt") instead of the literal "Image"; set coverAlt in the
MetadataViewerComponent to a book-specific string (e.g., `${book.title} cover`
or `${metadata.title} cover`) when a title exists, and set it to an empty string
("") when the image is purely decorative or the title is absent.

In
`@frontend/src/app/features/notebook/components/notebook/notebook.component.ts`:
- Line 21: The ebook grouping path still calls getDirectThumbnailUrl(...) which
always returns a string, preventing the nullable thumbnailUrl contract from
taking effect; change the ebook branch to use the nullable thumbnail source
(thumbnailUrl) instead of getDirectThumbnailUrl and ensure any cover-update
metadata is forwarded through that same nullable property so the placeholder
fallback can trigger for ebooks without artwork. Locate the ebook grouping logic
and replace the direct call to getDirectThumbnailUrl with the nullable
thumbnailUrl value (or a nullable wrapper function) and propagate cover-update
metadata alongside thumbnailUrl when building the ebook entry.

---

Nitpick comments:
In
`@frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.html`:
- Around line 17-18: The placeholder app-cover-placeholder should have the same
click and keyboard behavior as the image cover: add the same click handler used
by the image cover (e.g., (click)="openMetadata(bookInfo())") and make it
keyboard-accessible by adding tabindex="0" and keydown handlers for Enter/Space
that call the same openMetadata method; also add role="button" and an
appropriate aria-label (e.g., aria-label="Open book metadata for
{{bookInfo().title}}") so the fallback cover (when coverUrl is missing) behaves
identically to the image cover.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f17033a-42f6-4375-ba45-ce0f24c1703f

📥 Commits

Reviewing files that changed from the base of the PR and between f1cbcf3 and 0d8d0b8.

📒 Files selected for processing (34)
  • frontend/src/app/features/book/components/book-browser/book-card/book-card.component.html
  • frontend/src/app/features/book/components/book-browser/book-card/book-card.component.ts
  • frontend/src/app/features/book/components/book-browser/book-table/book-table.component.html
  • frontend/src/app/features/book/components/book-browser/book-table/book-table.component.ts
  • frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.html
  • frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.ts
  • frontend/src/app/features/book/components/book-searcher/book-searcher.component.html
  • frontend/src/app/features/book/components/book-searcher/book-searcher.component.ts
  • frontend/src/app/features/book/components/duplicate-merger/duplicate-merger.component.html
  • frontend/src/app/features/book/components/duplicate-merger/duplicate-merger.component.ts
  • frontend/src/app/features/book/components/series-page/series-page.component.html
  • frontend/src/app/features/book/components/series-page/series-page.component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.html
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-tabs/metadata-tabs.component.html
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-tabs/metadata-tabs.component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.html
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts
  • frontend/src/app/features/notebook/components/notebook/notebook.component.html
  • frontend/src/app/features/notebook/components/notebook/notebook.component.ts
  • frontend/src/app/features/readers/cbx-reader/layout/sidebar/cbx-sidebar.component.html
  • frontend/src/app/features/readers/cbx-reader/layout/sidebar/cbx-sidebar.component.ts
  • frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.html
  • frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.scss
  • frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.ts
  • frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.html
  • frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.ts
  • frontend/src/app/features/series-browser/components/series-card/series-card.component.html
  • frontend/src/app/features/series-browser/components/series-card/series-card.component.spec.ts
  • frontend/src/app/features/series-browser/components/series-card/series-card.component.ts
  • frontend/src/app/shared/components/cover-generator/cover-generator.component.spec.ts
  • frontend/src/app/shared/components/cover-generator/cover-generator.component.ts
  • frontend/src/app/shared/service/url-helper.service.spec.ts
  • frontend/src/app/shared/service/url-helper.service.ts
💤 Files with no reviewable changes (1)
  • frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.scss
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
frontend/src/**/*.{ts,tsx,html,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation in TypeScript, HTML, and SCSS files

Files:

  • frontend/src/app/features/book/components/duplicate-merger/duplicate-merger.component.ts
  • frontend/src/app/features/notebook/components/notebook/notebook.component.ts
  • frontend/src/app/features/book/components/book-browser/book-table/book-table.component.ts
  • frontend/src/app/features/book/components/book-searcher/book-searcher.component.ts
  • frontend/src/app/features/notebook/components/notebook/notebook.component.html
  • frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.ts
  • frontend/src/app/features/series-browser/components/series-card/series-card.component.spec.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-tabs/metadata-tabs.component.ts
  • frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.html
  • frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.html
  • frontend/src/app/features/readers/cbx-reader/layout/sidebar/cbx-sidebar.component.html
  • frontend/src/app/features/readers/cbx-reader/layout/sidebar/cbx-sidebar.component.ts
  • frontend/src/app/features/book/components/book-searcher/book-searcher.component.html
  • frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.ts
  • frontend/src/app/features/series-browser/components/series-card/series-card.component.html
  • frontend/src/app/features/book/components/book-browser/book-card/book-card.component.html
  • frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.ts
  • frontend/src/app/features/book/components/book-browser/book-table/book-table.component.html
  • frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.html
  • frontend/src/app/features/series-browser/components/series-card/series-card.component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-tabs/metadata-tabs.component.html
  • frontend/src/app/features/book/components/series-page/series-page.component.ts
  • frontend/src/app/features/book/components/book-browser/book-card/book-card.component.ts
  • frontend/src/app/features/book/components/series-page/series-page.component.html
  • frontend/src/app/shared/service/url-helper.service.spec.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts
  • frontend/src/app/shared/components/cover-generator/cover-generator.component.spec.ts
  • frontend/src/app/features/book/components/duplicate-merger/duplicate-merger.component.html
  • frontend/src/app/shared/components/cover-generator/cover-generator.component.ts
  • frontend/src/app/shared/service/url-helper.service.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.html
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.html
frontend/src/app/**/*.component.ts

📄 CodeRabbit inference engine (AGENTS.md)

Keep Angular code on standalone components. Do not add NgModules

Files:

  • frontend/src/app/features/book/components/duplicate-merger/duplicate-merger.component.ts
  • frontend/src/app/features/notebook/components/notebook/notebook.component.ts
  • frontend/src/app/features/book/components/book-browser/book-table/book-table.component.ts
  • frontend/src/app/features/book/components/book-searcher/book-searcher.component.ts
  • frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-tabs/metadata-tabs.component.ts
  • frontend/src/app/features/readers/cbx-reader/layout/sidebar/cbx-sidebar.component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.ts
  • frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.ts
  • frontend/src/app/features/series-browser/components/series-card/series-card.component.ts
  • frontend/src/app/features/book/components/series-page/series-page.component.ts
  • frontend/src/app/features/book/components/book-browser/book-card/book-card.component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts
  • frontend/src/app/shared/components/cover-generator/cover-generator.component.ts
frontend/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/app/**/*.{ts,tsx}: Prefer inject() over constructor injection
Follow frontend/eslint.config.js: component selectors use app-*, directive selectors use app*, and any is disallowed

Files:

  • frontend/src/app/features/book/components/duplicate-merger/duplicate-merger.component.ts
  • frontend/src/app/features/notebook/components/notebook/notebook.component.ts
  • frontend/src/app/features/book/components/book-browser/book-table/book-table.component.ts
  • frontend/src/app/features/book/components/book-searcher/book-searcher.component.ts
  • frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.ts
  • frontend/src/app/features/series-browser/components/series-card/series-card.component.spec.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-tabs/metadata-tabs.component.ts
  • frontend/src/app/features/readers/cbx-reader/layout/sidebar/cbx-sidebar.component.ts
  • frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.ts
  • frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.ts
  • frontend/src/app/features/series-browser/components/series-card/series-card.component.ts
  • frontend/src/app/features/book/components/series-page/series-page.component.ts
  • frontend/src/app/features/book/components/book-browser/book-card/book-card.component.ts
  • frontend/src/app/shared/service/url-helper.service.spec.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts
  • frontend/src/app/shared/components/cover-generator/cover-generator.component.spec.ts
  • frontend/src/app/shared/components/cover-generator/cover-generator.component.ts
  • frontend/src/app/shared/service/url-helper.service.ts
frontend/src/**/*.{test,spec}.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for tests in the frontend

Files:

  • frontend/src/app/features/series-browser/components/series-card/series-card.component.spec.ts
  • frontend/src/app/shared/service/url-helper.service.spec.ts
  • frontend/src/app/shared/components/cover-generator/cover-generator.component.spec.ts
🧠 Learnings (2)
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to frontend/src/app/**/*.component.ts : Keep Angular code on standalone components. Do not add NgModules

Applied to files:

  • frontend/src/app/features/book/components/duplicate-merger/duplicate-merger.component.ts
  • frontend/src/app/features/notebook/components/notebook/notebook.component.ts
  • frontend/src/app/features/book/components/book-browser/book-table/book-table.component.ts
  • frontend/src/app/features/book/components/book-searcher/book-searcher.component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-tabs/metadata-tabs.component.ts
  • frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.ts
  • frontend/src/app/features/book/components/book-browser/book-card/book-card.component.ts
  • frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to frontend/src/**/*.{test,spec}.ts : Use Vitest for tests in the frontend

Applied to files:

  • frontend/src/app/shared/components/cover-generator/cover-generator.component.spec.ts
🔇 Additional comments (37)
frontend/src/app/features/book/components/book-searcher/book-searcher.component.ts (1)

12-12: Placeholder component wiring is correct.

The CoverPlaceholderComponent import and standalone imports registration are clean and match the fallback rendering intent.

Also applies to: 31-31

frontend/src/app/features/book/components/duplicate-merger/duplicate-merger.component.ts (1)

18-18: Standalone import update looks good.

CoverPlaceholderComponent is correctly added for template fallback support.

Also applies to: 43-43

frontend/src/app/features/notebook/components/notebook/notebook.component.ts (1)

15-15: Import wiring for placeholder rendering is correct.

The CoverPlaceholderComponent registration itself is correct and aligns with the template fallback.

Also applies to: 47-47

frontend/src/app/features/book/components/book-browser/book-table/book-table.component.ts (1)

10-10: Book table placeholder integration looks correct.

The added import and standalone imports update are consistent with the null-cover fallback pattern.

Also applies to: 33-34

frontend/src/app/features/series-browser/components/series-card/series-card.component.spec.ts (1)

96-96: Spec update matches the new nullable API contract.

toBeNull() here is the right assertion for missing cover indices.

frontend/src/app/features/readers/cbx-reader/layout/sidebar/cbx-sidebar.component.ts (1)

8-8: Sidebar placeholder dependency is correctly registered.

No concerns with this standalone import change.

Also applies to: 15-15

frontend/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.ts (1)

10-10: Metadata editor placeholder wiring is good.

The new component import and registration are correct for fallback rendering in the template.

Also applies to: 61-61

frontend/src/app/features/notebook/components/notebook/notebook.component.html (1)

141-145: Template fallback branch is implemented correctly.

The @if/@else rendering between <img> and <app-cover-placeholder> is clean and readable.

frontend/src/app/features/readers/ebook-reader/layout/sidebar/sidebar.component.ts (1)

8-8: Standalone imports update looks correct

CoverPlaceholderComponent is correctly imported and registered in imports, matching the template fallback usage.

Also applies to: 15-15

frontend/src/app/features/readers/cbx-reader/layout/sidebar/cbx-sidebar.component.html (1)

11-12: Fallback rendering is clean and consistent

Good use of <app-cover-placeholder> in the no-cover branch with title/author bindings.

frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.html (1)

15-15: Shared placeholder usage is a good simplification

Replacing the inline fallback with the shared component here is clean and consistent.

frontend/src/app/features/book/components/duplicate-merger/duplicate-merger.component.html (1)

189-196: Cover URL guard and fallback are well implemented

The as coverUrl guard with a placeholder fallback avoids invalid image sources and keeps the row rendering robust.

frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-tabs/metadata-tabs.component.ts (1)

14-14: Standalone component wiring is correct

CoverPlaceholderComponent is added cleanly to imports and aligns with template usage.

Based on learnings: "Applies to frontend/src/app/**/*.component.ts : Keep Angular code on standalone components. Do not add NgModules".

Also applies to: 77-78

frontend/src/app/features/series-browser/components/series-card/series-card.component.html (1)

4-19: Stacked cover fallback is implemented correctly

Great branch parity here—placeholder keeps the same class/positioning hooks as the image variant.

frontend/src/app/features/book/components/book-card-lite/book-card-lite-component.ts (1)

45-50: Nullable thumbnail flow looks correct.

The string | null return type and URL selection logic are consistent with the placeholder-based rendering path.

frontend/src/app/features/book/components/book-browser/book-table/book-table.component.html (1)

54-77: Cover fallback handling is consistent here.

Good update: both thumbnail and tooltip now gracefully render a placeholder when the URL is unavailable.

frontend/src/app/features/book/components/book-searcher/book-searcher.component.html (1)

52-60: Nice null-cover fallback in search results.

This avoids broken image states and keeps rendering predictable for missing artwork.

frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts (1)

1285-1290: getBookCoverUrl nullability update is solid.

Returning string | null here cleanly supports the new template-level placeholder branch.

frontend/src/app/features/readers/ebook-reader/dialogs/metadata-dialog.component.ts (1)

3-12: Imports update looks good.

Standalone imports now include the placeholder component and remain consistent with the dialog template behavior.

frontend/src/app/features/book/components/series-page/series-page.component.ts (1)

484-489: Series thumbnail nullability change is clean.

getBookThumbnailUrl now correctly propagates missing-cover states for template-level placeholder rendering.

frontend/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-tabs/metadata-tabs.component.html (1)

76-103: LGTM!

The conditional rendering pattern using @if (...; as coverUrl) correctly handles the nullable return type from the URL helper methods. The fallback to <app-cover-placeholder> with appropriate title, author, and size inputs is well-implemented.

frontend/src/app/features/series-browser/components/series-card/series-card.component.ts (1)

38-45: LGTM!

The return type change to string | null and returning null for missing cover books aligns correctly with the updated UrlHelperService contract. The template can now use @if to conditionally render the placeholder.

frontend/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.html (2)

20-31: LGTM!

The ebook cover section correctly uses conditional rendering with the nullable getCoverUrl() return value, falling back to the CSS placeholder with appropriate dimensions and inputs.


101-113: LGTM!

The audiobook cover section mirrors the ebook cover pattern with correct square dimensions (200x200px) for the placeholder fallback.

frontend/src/app/features/book/components/book-browser/book-card/book-card.component.ts (2)

86-86: LGTM!

The field type change from string initialized to '' to string | null initialized to null correctly reflects the updated nullable contract from UrlHelperService.


218-220: LGTM!

The getter return type update to string | null maintains type safety and enables proper conditional rendering in the template.

frontend/src/app/features/book/components/series-page/series-page.component.html (3)

35-44: LGTM!

The hero cover section correctly conditionally renders the thumbnail image when available, falling back to the CSS placeholder with size="lg" for the larger hero display.


193-200: LGTM!

The "next up" thumbnail correctly uses conditional rendering and falls back to the smaller placeholder (size="sm"), appropriately sized for the compact card layout.


321-328: LGTM!

The book list thumbnail follows the same pattern with size="sm" for the compact list view, maintaining consistency across the component.

frontend/src/app/shared/service/url-helper.service.spec.ts (2)

50-70: LGTM!

The new tests correctly verify the nullable return behavior when coverUpdatedOn or audiobookCoverUpdatedOn parameters are not provided. Each URL helper method has appropriate null-case coverage.


76-81: LGTM!

Good coverage for the auth-unavailable edge case, verifying that direct URLs are returned without a token when authentication is not present.

frontend/src/app/shared/components/cover-generator/cover-generator.component.spec.ts (2)

21-47: LGTM!

Comprehensive DOM-focused tests for CoverPlaceholderComponent covering text rendering, background color application, and size class assignment. The use of fixture.componentRef.setInput() is the correct approach for testing Angular signal-based inputs.


50-66: LGTM!

Good unit test coverage for the coverColorFor utility function, verifying determinism, differentiation for different inputs, and valid hex color format output.

frontend/src/app/shared/components/cover-generator/cover-generator.component.ts (2)

3-19: LGTM!

The color generation system is well-designed: a curated palette of dark colors combined with a deterministic hash function ensures consistent placeholder colors for the same book across the application while providing visual variety.


23-105: LGTM!

The CoverPlaceholderComponent is a clean, self-contained standalone component using Angular's modern signal-based inputs and computed properties. The inline template with CSS line-clamping provides a simple but effective placeholder that aligns with the PR's goal of replacing the complex SVG generation pipeline.

frontend/src/app/shared/service/url-helper.service.ts (2)

25-27: LGTM - Nullable return enables placeholder fallback.

The return type change to string | null correctly signals when no cover exists, allowing templates to conditionally render the CSS placeholder. The query string pattern (using timestamp directly) is consistent with getDirectThumbnailUrl.


38-55: Consistent nullable pattern across all cover/thumbnail methods.

All four URL methods (getCoverUrl, getAudiobookCoverUrl, getAudiobookThumbnailUrl, and the earlier getThumbnailUrl) now follow the same contract: return null when no cover timestamp exists, enabling the UI to render CSS placeholders instead of making requests for non-existent images. The context snippets confirm callers properly guard against null values.

@imajes

imajes commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

@alexhb1 i had a quick look at the rabbit comments and they all look reasonable - mind taking a pass?

@alexhb1

alexhb1 commented Mar 31, 2026

Copy link
Copy Markdown
Member Author

Just working through them now :)

@alexhb1

alexhb1 commented Mar 31, 2026

Copy link
Copy Markdown
Member Author

this one is good to go

@imajes imajes 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.

This is great, thanks @alexhb1 :)

@imajes imajes merged commit fb2bde3 into grimmory-tools:develop Mar 31, 2026
14 checks passed
@alexhb1 alexhb1 deleted the perf-2-icon-placeholder branch March 31, 2026 19:43
zachyale pushed a commit to zachyale/grimmory that referenced this pull request Apr 17, 2026
…ls#292)

* chore(ui): replace SVG placeholders with CSS alternative

* fix(ui): Placeholder review followup
zachyale pushed a commit to zachyale/grimmory that referenced this pull request Apr 17, 2026
…ls#292)

* chore(ui): replace SVG placeholders with CSS alternative

* fix(ui): Placeholder review followup
zachyale pushed a commit that referenced this pull request Apr 17, 2026
* chore(ui): replace SVG placeholders with CSS alternative

* fix(ui): Placeholder review followup
zachyale pushed a commit that referenced this pull request Apr 22, 2026
* chore(ui): replace SVG placeholders with CSS alternative

* fix(ui): Placeholder review followup
dsmouse pushed a commit to dsmouse/grimmory that referenced this pull request May 11, 2026
…ls#292)

* chore(ui): replace SVG placeholders with CSS alternative

* fix(ui): Placeholder review followup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants