- Deleted templates.CollectionDetailData - using templates.CollectionData everywhere - Deleted templates.BookData - using handlers.BookInfo everywhere - Deleted templates.DeviceData - using handlers.DeviceInfo everywhere - Deleted templates.ProgressItemData - using handlers.ProgressWithMedia everywhere - Deleted templates.convertDevices() helper - Use handlers types directly in templates - Enhanced handlers.ProgressWithMedia with device metadata fields - Added handlers.getDeviceIcon() helper - Updated all templates to import handlers package - Cleaned up unused imports This aligns codebase with templ's design philosophy (use Go types directly, no parallel type system)
14 KiB
Type Duplication Refactoring Plan
Document Purpose: Consolidate duplicate type definitions across templates/ and handlers/ packages to align with templ's design philosophy and reduce maintenance burden.
Status: Planning Phase Last Updated: 2026-02-12 Separation from: IMPLEMENTATION_EXACT.md (this is independent refactoring)
Core Principle: API wins - When there's ambiguity between template types and API/handler types, the API/handler types take precedence.
Executive Summary
Problem: Codebase maintains parallel type systems:
templates/types.go- Template-specific typeshandlers/*.go- API response types
Core Principle: API wins - when in doubt, API/handler types take precedence
Why "API wins"?
- API is public contract: Handler types define your public API surface - these are more stable
- Templates are presentation: HTML templates can format any Go type - they're flexible
- Single source of truth: One authoritative type definition vs two parallel definitions
- Json serialization: Handler types already have
json:"..."tags for API responses - Future API clients: If you add mobile apps or CLI tools, they need handler types anyway
Trade-offs
Pro: Simpler codebase, one type to maintain, automatic API/HTML consistency Con: Templates may have unused fields visible in scope (acceptable - templ handles this well)
Impact:
- ~40 lines of duplicate type definitions
- Unnecessary conversion functions
- Maintenance burden (update 2 places for 1 change)
- Violates templ's design philosophy
Solution: Use handler types directly in templates, eliminate template-specific duplicates.
Current State Analysis
Duplicates Found
| Template Type | Handler Type | Overlap | Action |
|---|---|---|---|
DeviceData |
DeviceInfo |
90% | DELETE DeviceData |
BookData |
collections.BookInfo |
100% | DELETE BookData |
CollectionDetailData |
CollectionData |
100% | DELETE CollectionDetailData |
ProgressItemData |
ProgressWithMedia |
70% | MERGE (different fields) |
QueueItemData |
QueueItemResponse |
60% | KEEP (template uses subset) |
Already Using Handler Types (Good Patterns)
✅ Conflicts template uses handlers.ConflictDetailResponse
✅ Queue template uses handlers.QueueItemResponse
This is the pattern we want everywhere.
Refactoring Plan
Phase 1: Eliminate Complete Duplicates
1.1 Delete CollectionDetailData
Files to Modify:
templates/types.go(lines 26-32) - DELETE structtemplates/collection_rules.templ(line 3) - Change signaturetemplates/collections.templ(line 213) - Change signature
Changes:
Delete from templates/types.go:
type CollectionDetailData struct {
ID string
Name string
Description string
Color string
Icon string
}
Update templates/collection_rules.templ:3:
// BEFORE:
templ CollectionRules(user User, collection CollectionDetailData) {
// AFTER:
templ CollectionRules(user User, collection templates.CollectionData) {
Update templates/collections.templ:213:
// BEFORE:
templ CollectionDetail(user User, collection CollectionDetailData, books []BookData) {
// AFTER:
templ CollectionDetail(user User, collection templates.CollectionData, books []collections.BookInfo) {
Test: go test ./templates/... after template regeneration
1.2 Delete BookData
Files to Modify:
templates/types.go(lines 34-39) - DELETE structtemplates/collections.templ(line 213) - Change books param
Changes:
Delete from templates/types.go:
type BookData struct {
MediaItemID string
Title string
Author string
CoverImagePath string
}
Update templates/collections.templ:213:
// BEFORE:
templ CollectionDetail(user User, collection templates.CollectionData, books []BookData) {
// AFTER:
templ CollectionDetail(user User, collection templates.CollectionData, books []collections.BookInfo) {
Test: View collection detail page, verify books display correctly
1.3 Delete DeviceData and ConvertDevices
Files to Modify:
templates/types.go(lines 48-57) - DELETE structtemplates/devices.templ(line 3) - Change signatureinternal/router/helpers.go(lines 46-67) - DELETE functioninternal/router/frontend.go(lines 161-169) - Remove conversion
Changes:
Delete from templates/types.go:
type DeviceData struct {
ID string
DeviceName string
DeviceType string
SyncEnabled bool
AutoSync bool
SyncFrequency int32
LastSync string
LastSeen string
}
Delete from internal/router/helpers.go:
func convertDevices(deviceInfos []handlers.DeviceInfo) []templates.DeviceData {
// ... entire function (lines 46-67)
}
Update templates/devices.templ:3:
// BEFORE:
templ Devices(user User, devices []DeviceData, pendingRegistrations []PendingRegistrationData) {
// AFTER:
templ Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []PendingRegistrationData) {
Update templates/devices.templ template logic:
Find these patterns and update:
// BEFORE:
if device.LastSync != "" {
<span>{ device.LastSync }</span>
}
// AFTER:
if device.LastSync != nil {
<span>{ device.LastSync.Format("2006-01-02 15:04") }</span>
}
Update internal/router/frontend.go:161-169:
// BEFORE:
deviceData, err := cfg.DeviceHandler.GetDevicesData(c)
if err != nil {
return c.HTML(http.StatusInternalServerError, "Error loading devices")
}
pendingData, err := cfg.DeviceHandler.GetPendingRegistrationsData(c)
if err != nil {
return c.HTML(http.StatusInternalServerError, "Error loading pending")
}
deviesList := convertDevices(deviceData)
pendingList := convertPending(pendingData)
// AFTER:
deviceData, err := cfg.DeviceHandler.GetDevicesData(c)
if err != nil {
return c.HTML(http.StatusInternalServerError, "Error loading devices")
}
pendingData, err := cfg.DeviceHandler.GetPendingRegistrationsData(c)
if err != nil {
return c.HTML(http.StatusInternalServerError, "Error loading pending")
}
// No conversion - use handler data directly
Test: Devices page renders correctly with proper date formatting
Phase 2: Eliminate Remaining Template Types
2.1 ProgressItemData → Use ProgressWithMedia
Current Issue:
handlers.ProgressWithMediaexists as API response typetemplates.ProgressItemDataduplicates it with minor variations
Decision: API wins - Use handlers.ProgressWithMedia everywhere
Files to Modify:
internal/handlers/progress.go- UpdateProgressWithMediastructtemplates/types.go- DELETEProgressItemDatatemplates/progress.templ- Usehandlers.ProgressWithMedia
Changes:
Add fields to internal/handlers/progress.go:ProgressWithMedia:
// ADD fields to existing struct:
type ProgressWithMedia struct {
MediaItemID uuid.UUID `json:"media_item_id"`
Title string `json:"title"`
Author string `json:"author"`
CoverImagePath string `json:"cover_image_path"`
Percentage float64 `json:"percentage"`
CurrentPage int32 `json:"current_page"`
TotalPages int32 `json:"total_pages"`
LastReadAt time.Time `json:"last_read_at"`
Epubcfi string `json:"epubcfi"`
LastSyncDevice string `json:"last_sync_device"`
DeviceName string `json:"device_name,omitempty"` // ADD
DeviceType string `json:"device_type,omitempty"` // ADD
DeviceIcon string `json:"device_icon,omitempty"` // ADD
}
Update internal/handlers/progress.go:GetAllProgress() to populate new device fields.
Delete from templates/types.go (lines 99-112):
type ProgressItemData struct {
// ... delete entire struct
}
Update templates/progress.templ:3:
// BEFORE:
templ Progress(user User, progressData []ProgressItemData) {
// AFTER:
templ Progress(user User, progressData []handlers.ProgressWithMedia) {
Test: Progress page displays device names and icons
Delete from templates/types.go (lines 99-112):
type ProgressItemData struct {
// ... delete entire struct
}
Update templates/progress.templ:3:
// BEFORE:
templ Progress(user User, progressData []ProgressItemData) {
// AFTER:
templ Progress(user User, progressData []handlers.ProgressWithMedia) {
Test: Progress page displays device names and icons
Phase 3: Update Get*Data Functions
Issue: Several handler functions return handler types but aren't used directly by templates due to conversion layer.
Files to Modify:
internal/router/frontend.go- Remove conversions
Changes:
Devices (already done in Phase 1.3):
GetDevicesData()returns[]handlers.DeviceInfo- Template uses directly
Progress:
// frontend.go - update progress route
protected.GET("/progress-page", func(c echo.Context) error {
// ...
progressData, err := cfg.Handler.GetAllProgressData(c)
// Returns []handlers.ProgressWithMedia directly - no conversion
// ...
})
Collections:
// collections.go - update to return handler types
func (h *CollectionHandler) GetCollectionBooksData(c echo.Context) ([]collections.BookInfo, error) {
// Already returns BookInfo - just update template
}
Summary Table
| Phase | Files Changed | Lines Removed | Lines Added | Net Change |
|---|---|---|---|---|
| 1.1 CollectionDetailData | 3 | 7 | 2 | -5 |
| 1.2 BookData | 3 | 6 | 0 | -6 |
| 1.3 DeviceData | 4 | 24 | 5 | -19 |
| 2.1 ProgressItemData | 4 | 14 | 3 | -11 |
| Total | 14 | 51 | 10 | -41 |
Note: QueueItemData already uses handler type (good pattern). Conflicts already uses handler type (good pattern).
Testing Checklist
After each phase:
Phase 1.1 (CollectionDetailData)
- View collection rules page
- Edit collection rules
- View collection detail page
- Add books to collection
Phase 1.2 (BookData)
- View collection detail page
- Browse books in collection
- Book covers display correctly
Phase 1.3 (DeviceData)
- Devices page loads
- Device cards display
- Last sync/last seen dates formatted
- Device type icons show correctly
Phase 2.1 (ProgressItemData)
- Progress page loads
- Progress entries show device names/icons
- Percentage display works
- Cover images display
Rollback Plan
If refactoring breaks functionality:
- Git stash changes before starting
- Commit after each successful phase
- Revert individual commits if needed
- Keep conversion functions commented out temporarily
# Before starting
git stash push -m "pre-refactor-state"
# After Phase 1.1
git commit -m "refactor: delete CollectionDetailData, use CollectionData"
# If Phase 1.2 breaks
git revert HEAD
# Continue with 1.3
Benefits
Code Quality
- ✅ Aligns with templ's design philosophy
- ✅ Single source of truth for types
- ✅ Reduced code duplication
- ✅ API-first: Handler types are authoritative
Maintenance
- ✅ Update types in 1 place, not 2
- ✅ Less conversion logic to maintain
- ✅ Clearer data flow
- ✅ Template changes automatically get API updates
Consistency
- ✅ All templates use handlers types (like Queue/Conflicts already do)
- ✅ API and HTML use same type definitions
- ✅ 41 fewer lines of code
- ✅ "API wins" principle eliminates ambiguity
Future Considerations
For IMPLEMENTATION_EXACT.md
After this refactoring, adding AuthToken to devices is simpler:
- Add
AuthToken stringtohandlers.DeviceInfostruct - Add to
GetDevicesData()function return values - Template automatically gets it (no conversion layer)
This solves the critical issue found in IMPLEMENTATION_EXACT.md analysis automatically.
For New Features
- Define handler types with json tags first
- Templates use same types directly
- Only create template-specific types for:
- Computed/derived fields not in API
- Combining data from multiple sources
- Simplifying complex nested structures
Execution Order
- Run tests to ensure baseline:
go test ./... - Create feature branch:
git checkout -b refactor/types-consolidation - Execute Phase 1.1 (CollectionDetailData) - DELETE template type, use API type
- Commit and test
- Execute Phase 1.2 (BookData) - DELETE template type, use API type
- Commit and test
- Execute Phase 1.3 (DeviceData) - DELETE template type, use API type
- Commit and test
- Execute Phase 2.1 (ProgressItemData) - DELETE template type, enhance API type
- Final commit and test
- Update IMPLEMENTATION_EXACT.md to reference handler types only
Total Estimated Time: 2-3 hours with testing
Key Mindset: Every change removes a template type, never the reverse. API types are authoritative.
Questions for User
-
Progress template updates: Since we're using
handlers.ProgressWithMedia(API type) in templates, any template formatting changes (like date formatting) should be done in the template itself. Is this acceptable? -
Testing: Do you have automated template tests, or is this manual testing only?
-
Implementation plan: Should I update IMPLEMENTATION_EXACT.md after this refactoring to reference
handlers.DeviceInfoinstead ofDeviceData? -
Json tags on handler types: Templates don't need
json:"..."tags - should we document that these are for API responses only?