From 3a7c8e18c930e5d09b30f5abcd8e62c43905f52e Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Mon, 30 Mar 2026 21:29:08 -0400 Subject: [PATCH] docs: add frontend implementation plan for comic metadata fields Add comprehensive planning document for displaying new ComicInfo.xml metadata fields on the book detail page. New fields to be added: - Reading direction (RTL/LTR/vertical) badge - Community rating display (0-10 scale) - Universal fields: series count, volume, imprint, age rating - Comic-specific: manga type, story arc, scan info, B&W flag Implementation approach: - SSR-first rendering (no client-side fetching) - TailwindCSS only (no custom CSS) - Conditional display based on field validity - Follows existing template patterns This document provides step-by-step implementation guidance with code examples and testing scenarios for the frontend team. Related: IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md --- ...TEND_IMPLEMENTATION_PLAN_COMIC_METADATA.md | 510 ++++++++++++++++++ 1 file changed, 510 insertions(+) create mode 100644 FRONTEND_IMPLEMENTATION_PLAN_COMIC_METADATA.md diff --git a/FRONTEND_IMPLEMENTATION_PLAN_COMIC_METADATA.md b/FRONTEND_IMPLEMENTATION_PLAN_COMIC_METADATA.md new file mode 100644 index 0000000..8f159fc --- /dev/null +++ b/FRONTEND_IMPLEMENTATION_PLAN_COMIC_METADATA.md @@ -0,0 +1,510 @@ +# Frontend Implementation Plan: Add Comic Metadata Fields to Book Detail Page + +**Status:** Planning Document (Not Implemented) +**Date:** March 30, 2026 +**Scope:** Display new comic/manga metadata fields on the book detail page + +--- + +## Overview + +This document outlines how to add the new ComicInfo.xml metadata fields to the book detail page frontend. These fields are already added to the database schema and backend models (per `IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md`). + +## New Fields to Display + +### Universal Fields (apply to all formats: ebooks, audiobooks, comics) +- `series_count` - Total items in series +- `volume` - Volume/omnibus number +- `imprint` - Publisher imprint (e.g., Vertigo, HarperCollinsEpic) +- `age_rating` - Age rating: Everyone, Teen, Mature, Adult +- `web_url` - URL to info page (Goodreads, ComicVine, MangaUpdates, etc.) +- `metadata_notes` - Notes from metadata files (ComicInfo.xml, EPUB, PDF) +- `community_rating` - Pre-existing community rating (0.0-10.0) - **distinct from user ratings** + +### Comic-Specific Fields +- `manga_type` - Raw Manga field: unknown, no, yes, yes_and_right_to_left +- `reading_direction` - Computed reading direction: auto, ltr, rtl, vertical +- `story_arc` - Story arc name (e.g., "The Dark Phoenix Saga", "Civil War") +- `is_black_and_white` - Black and white flag +- `alternate_info` - JSONB: {alternate_series, alternate_number, alternate_count} +- `scan_information` - Scan information (scanner group, resolution, etc.) +- `summary` - Summary from ComicInfo.xml (may merge with description) + +--- + +## Current Book Detail Page Structure + +**File:** `templates/book_detail.templ` + +### Current Layout Sections: + +1. **Top Section** (lines 23-130) + - Cover image (left) + - Title, author, action buttons (right) + - Rating display (user ratings) + - Series badge + - Description/Synopsis + +2. **Progress Section** (lines 132-185) + - Reading progress bar + - Progress stats grid + +3. **Metadata Grid** (lines 186-253) + - Publication info (Publisher, Published, ISBN, Language, Edition, Pages, Genre, Copyright Year) + - Technical info (Format, File Size) + - External links section + +4. **Collections Section** (lines 347-367) + +--- + +## Implementation Plan + +### Step 1: Add Reading Direction Badge (Display Priority: HIGH) + +**Location:** After Series Badge (line 117) + +**Rationale:** Reading direction is critical for manga/comic readers. Display prominently like the series badge. + +```templ + +if book.ReadingDirection.Valid && book.ReadingDirection.String != "" && book.ReadingDirection.String != "auto" { +
+ + 📖 { strings.ToUpper(book.ReadingDirection.String) } + +
+} +``` + +**Styling Notes:** +- Use `var(--accent)` for consistency with series badge +- Only show if not "auto" (i.e., explicitly set to ltr, rtl, or vertical) +- Icon: 📖 for visual clarity + +--- + +### Step 2: Add Community Rating Display (Display Priority: HIGH) + +**Location:** After User Rating Display (line 103) + +**Rationale:** Community rating complements user rating. Show side-by-side for comparison. + +```templ + +if book.CommunityRating.Valid && book.CommunityRating.Float64 > 0 { +
+ + Community Rating: + + @templ.Raw(renderStars(getBookRating(int(book.CommunityRating.Float64 * 2)))) + + + ({ fmt.Sprintf("%.1f", book.CommunityRating.Float64) } / 10) + + +
+} +``` + +**Styling Notes:** +- Smaller text than user rating (text-lg vs text-2xl) +- Gray/subtle color to distinguish from user rating +- Scale: Community rating is 0-10, user rating display is 0-5 stars + - Conversion: `community_rating * 2` to get 0-10 scale for star display + +--- + +### Step 3: Add Comic-Specific Badges (Display Priority: MEDIUM) + +**Location:** After Reading Direction Badge (line 117) + +**Rationale:** Group all manga/comic-specific badges together for easy scanning. + +```templ + +
+ + if book.AgeRating.Valid && book.AgeRating.String != "" { + + { book.AgeRating.String } + + } + + + if book.IsBlackAndWhite.Valid && book.IsBlackAndWhite.Bool { + + B&W + + } + + + if book.StoryArc.Valid && book.StoryArc.String != "" { + + 📚 { book.StoryArc.String } + + } +
+``` + +**Styling Notes:** +- Use subtle styling (smaller, outlined) to differentiate from primary badges +- Flex wrap for responsive layout +- Icons: 📚 for story arc + +--- + +### Step 4: Add Universal Series Info to Metadata Grid (Display Priority: HIGH) + +**Location:** In Metadata Grid section, after Series/Number fields + +**Note:** The current template doesn't display Series/Number in the metadata grid (only as badge). We'll add all series-related fields together. + +**Insertion Point:** After Genre field (line 235) + +```templ + +if book.SeriesCount.Valid && book.SeriesCount.Int32 > 0 { +
+

Series Count

+

{ book.SeriesCount.Int32 } items

+
+} +if book.Volume.Valid && book.Volume.Int32 > 0 { +
+

Volume

+

Vol. { book.Volume.Int32 }

+
+} +if book.Imprint.Valid && book.Imprint.String != "" { +
+

Imprint

+

{ book.Imprint.String }

+
+} +``` + +--- + +### Step 5: Add Comic-Specific Metadata to Grid (Display Priority: MEDIUM) + +**Location:** In Metadata Grid section, after Copyright Year (line 241) + +```templ + +if book.MangaType.Valid && book.MangaType.String != "" && book.MangaType.String != "unknown" { +
+

Manga Type

+

{ strings.ReplaceAll(book.MangaType.String, "_", " ") }

+
+} +if book.ScanInformation.Valid && book.ScanInformation.String != "" { +
+

Scan Info

+

{ book.ScanInformation.String }

+
+} +if book.AlternateInfo.Valid && len(book.AlternateInfo.Bytes) > 0 { +
+

Alternate Series

+

{ string(book.AlternateInfo.Bytes) }

+
+} +``` + +**Note for AlternateInfo:** This is JSONB data stored as `[]byte`. You may want to: +1. Parse the JSON and display formatted +2. Or create a helper function to extract specific fields + +Helper function example (add to `templates/helpers.go`): + +```go +// getAlternateSeries extracts the alternate series name from JSONB data +func getAlternateSeries(data pgtype.Bytes) string { + if !data.Valid || len(data.Bytes) == 0 { + return "" + } + var result map[string]interface{} + if err := json.Unmarshal(data.Bytes, &result); err != nil { + return "" + } + if series, ok := result["alternate_series"].(string); ok { + return series + } + return "" +} +``` + +Then in template: +```templ +if altSeries := getAlternateSeries(book.AlternateInfo); altSeries != "" { +
+

Alternate Series

+

{ altSeries }

+
+} +``` + +--- + +### Step 6: Add Summary Section (Display Priority: MEDIUM) + +**Location:** After Description/Synopsis section (line 128) + +**Rationale:** Summary from ComicInfo.xml may differ from Calibre description. Show both if present. + +```templ + +if book.Summary.Valid && book.Summary.String != "" && book.Summary.String != book.Description.String { +
+

Comic Summary

+
+ @UnsafeHTML( + bluemonday.UGCPolicy().Sanitize(book.Summary.String), + ).ToComponent() +
+
+} +``` + +--- + +### Step 7: Add Metadata Notes Section (Display Priority: LOW) + +**Location:** After Summary section (or at the bottom before Collections) + +**Rationale:** Metadata notes are technical/scanner information. Less important for end users. + +```templ + +if book.MetadataNotes.Valid && book.MetadataNotes.String != "" { +
+

Metadata Notes

+
+ { book.MetadataNotes.String } +
+
+} +``` + +--- + +### Step 8: Add Web URL Link (Display Priority: MEDIUM) + +**Location:** In External Links section (line 258) + +**Rationale:** Add web_url as another external link alongside Goodreads, OpenLibrary, etc. + +```templ + +if book.WebUrl.Valid && book.WebUrl.String != "" { + + 🔗 { getDomainName(book.WebUrl.String) } + +} +``` + +Helper function to extract domain name (add to `templates/helpers.go`): + +```go +// getDomainName extracts a clean domain name from URL for display +func getDomainName(rawURL string) string { + if rawURL == "" { + return "Source" + } + u, err := url.Parse(rawURL) + if err != nil { + return "Source" + } + // Return hostname without www. + hostname := u.Hostname() + return strings.TrimPrefix(hostname, "www.") +} +``` + +--- + +## TypeScript Updates (No Changes Required) + +**File:** `web/src/book-detail.ts` + +**Status:** No changes needed. This file only handles Alpine.js modal visibility functions. + +**Note:** The book detail page uses SSR (server-side rendering) exclusively. No client-side data fetching or Alpine.js data manipulation is needed for these new fields. + +--- + +## Template Helper Functions Needed + +Add these functions to `templates/helpers.go` (or existing helpers file): + +```go +package templates + +import ( + "encoding/json" + "net/url" + "strings" + + "github.com/jackc/pgx/v5/pgtype" +) + +// getAlternateSeries extracts the alternate series name from JSONB data +func getAlternateSeries(data pgtype.Bytes) string { + if !data.Valid || len(data.Bytes) == 0 { + return "" + } + var result map[string]interface{} + if err := json.Unmarshal(data.Bytes, &result); err != nil { + return "" + } + if series, ok := result["alternate_series"].(string); ok { + return series + } + return "" +} + +// getDomainName extracts a clean domain name from URL for display +func getDomainName(rawURL string) string { + if rawURL == "" { + return "Source" + } + u, err := url.Parse(rawURL) + if err != nil { + return "Source" + } + hostname := u.Hostname() + return strings.TrimPrefix(hostname, "www.") +} + +// formatAlternateInfo formats alternate series info as readable string +func formatAlternateInfo(data pgtype.Bytes) string { + if !data.Valid || len(data.Bytes) == 0 { + return "" + } + var result map[string]interface{} + if err := json.Unmarshal(data.Bytes, &result); err != nil { + return "" + } + + var parts []string + if series, ok := result["alternate_series"].(string); ok && series != "" { + parts = append(parts, series) + } + if num, ok := result["alternate_number"].(float64); ok && num > 0 { + parts = append(parts, fmt.Sprintf("#%d", int(num))) + } + if count, ok := result["alternate_count"].(float64); ok && count > 0 { + parts = append(parts, fmt.Sprintf("(of %d)", int(count))) + } + + return strings.Join(parts, " ") +} +``` + +--- + +## Implementation Checklist + +- [ ] **Step 1:** Add Reading Direction Badge (after line 117) +- [ ] **Step 2:** Add Community Rating Display (after line 103) +- [ ] **Step 3:** Add Comic-Specific Badges (after line 117) +- [ ] **Step 4:** Add Universal Series Info to Metadata Grid (after line 235) +- [ ] **Step 5:** Add Comic-Specific Metadata to Grid (after line 241) +- [ ] **Step 6:** Add Summary Section (after line 128) +- [ ] **Step 7:** Add Metadata Notes Section (before Collections) +- [ ] **Step 8:** Add Web URL Link (in External Links section) +- [ ] Add template helper functions to `templates/helpers.go` +- [ ] Test with manga book (RTL reading direction) +- [ ] Test with Western comic (LTR reading direction) +- [ ] Test with webtoon (vertical reading direction) +- [ ] Test with ebook (no comic metadata) +- [ ] Verify responsive layout on mobile/desktop +- [ ] Verify dark/light theme compatibility + +--- + +## Testing Scenarios + +### Test Case 1: Japanese Manga +**Expected Display:** +- Reading Direction: "RTL" badge +- Manga Type: "yes_and_right_to_left" in metadata grid +- Community Rating: displayed alongside user rating + +### Test Case 2: Western Comic +**Expected Display:** +- Reading Direction: "LTR" badge (or hidden if auto) +- Age Rating: "Teen" or "Mature" badge +- Story Arc: displayed if present +- Publisher/Imprint: displayed in metadata grid + +### Test Case 3: Webtoon/Manhwa +**Expected Display:** +- Reading Direction: "vertical" badge +- Language: "ko" (Korean) in metadata grid +- Format-specific metadata displayed + +### Test Case 4: Regular Ebook +**Expected Display:** +- No reading direction badge (or "auto" hidden) +- No comic-specific fields +- Only universal fields (series count, volume, imprint, age rating) if present + +--- + +## Design Principles Followed + +1. **✅ SSR-First:** All data server-side rendered, no client-side fetching +2. **✅ Progressive Enhancement:** Page works without JavaScript +3. **✅ TailwindCSS Only:** No custom CSS added +4. **✅ Existing Patterns:** Follows current badge/grid styling +5. **✅ Responsive:** Uses existing responsive grid classes +6. **✅ Theme-Aware:** Uses CSS variables (`var(--accent)`, `var(--text-secondary)`, etc.) +7. **✅ Accessible:** Semantic HTML, proper contrast +8. **✅ Conditional Rendering:** Only show fields if they have values +9. **✅ No Backend Changes:** Frontend-only, uses existing API/handler types + +--- + +## Related Documentation + +- `IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md` - Full backend implementation +- `PROJECT_GUIDELINES.md` - Project coding standards and conventions +- `templates/book_detail.templ` - Current book detail template +- `internal/handlers/media_detail.go` - MediaDetail struct definition + +--- + +## Notes + +- **No TypeScript changes needed:** All data is SSR'd from Go handlers +- **No API changes needed:** MediaDetail already embeds database.MediaItems with new fields +- **Template helpers:** May need helper functions for JSONB parsing and URL display +- **Styling consistency:** All new elements use existing CSS variables and Tailwind classes +- **Conditional display:** Most fields should only display if they have values (check `.Valid`) + +--- + +**End of Document**