# 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 types
- `handlers/*.go` - API response types
**Core Principle**: **API wins - when in doubt, API/handler types take precedence**
### Why "API wins"?
1. **API is public contract**: Handler types define your public API surface - these are more stable
2. **Templates are presentation**: HTML templates can format any Go type - they're flexible
3. **Single source of truth**: One authoritative type definition vs two parallel definitions
4. **Json serialization**: Handler types already have `json:"..."` tags for API responses
5. **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 struct
- `templates/collection_rules.templ` (line 3) - Change signature
- `templates/collections.templ` (line 213) - Change signature
**Changes**:
**Delete** from `templates/types.go`:
```go
type CollectionDetailData struct {
ID string
Name string
Description string
Color string
Icon string
}
```
**Update** `templates/collection_rules.templ:3`:
```templ
// BEFORE:
templ CollectionRules(user User, collection CollectionDetailData) {
// AFTER:
templ CollectionRules(user User, collection templates.CollectionData) {
```
**Update** `templates/collections.templ:213`:
```templ
// 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 struct
- `templates/collections.templ` (line 213) - Change books param
**Changes**:
**Delete** from `templates/types.go`:
```go
type BookData struct {
MediaItemID string
Title string
Author string
CoverImagePath string
}
```
**Update** `templates/collections.templ:213`:
```templ
// 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 struct
- `templates/devices.templ` (line 3) - Change signature
- `internal/router/helpers.go` (lines 46-67) - DELETE function
- `internal/router/frontend.go` (lines 161-169) - Remove conversion
**Changes**:
**Delete** from `templates/types.go`:
```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`:
```go
func convertDevices(deviceInfos []handlers.DeviceInfo) []templates.DeviceData {
// ... entire function (lines 46-67)
}
```
**Update** `templates/devices.templ:3`:
```templ
// 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:
```templ
// BEFORE:
if device.LastSync != "" {
{ device.LastSync }
}
// AFTER:
if device.LastSync != nil {
{ device.LastSync.Format("2006-01-02 15:04") }
}
```
**Update** `internal/router/frontend.go:161-169`:
```go
// 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.ProgressWithMedia` exists as API response type
- `templates.ProgressItemData` duplicates it with minor variations
**Decision**: **API wins** - Use `handlers.ProgressWithMedia` everywhere
**Files to Modify**:
- `internal/handlers/progress.go` - Update `ProgressWithMedia` struct
- `templates/types.go` - DELETE `ProgressItemData`
- `templates/progress.templ` - Use `handlers.ProgressWithMedia`
**Changes**:
**Add fields to** `internal/handlers/progress.go:ProgressWithMedia`:
```go
// 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):
```go
type ProgressItemData struct {
// ... delete entire struct
}
```
**Update** `templates/progress.templ:3`:
```templ
// 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):
```go
type ProgressItemData struct {
// ... delete entire struct
}
```
**Update** `templates/progress.templ:3`:
```templ
// 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**:
```go
// 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**:
```go
// 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:
1. **Git stash** changes before starting
2. **Commit after each successful phase**
3. **Revert individual commits** if needed
4. **Keep conversion functions** commented out temporarily
```bash
# 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 string` to `handlers.DeviceInfo` struct
- 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
1. **Run tests** to ensure baseline: `go test ./...`
2. **Create feature branch**: `git checkout -b refactor/types-consolidation`
3. **Execute Phase 1.1** (CollectionDetailData) - DELETE template type, use API type
4. **Commit and test**
5. **Execute Phase 1.2** (BookData) - DELETE template type, use API type
6. **Commit and test**
7. **Execute Phase 1.3** (DeviceData) - DELETE template type, use API type
8. **Commit and test**
9. **Execute Phase 2.1** (ProgressItemData) - DELETE template type, enhance API type
10. **Final commit and test**
11. **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
1. **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?
2. **Testing**: Do you have automated template tests, or is this manual testing only?
3. **Implementation plan**: Should I update IMPLEMENTATION_EXACT.md after this refactoring to reference `handlers.DeviceInfo` instead of `DeviceData`?
4. **Json tags on handler types**: Templates don't need `json:"..."` tags - should we document that these are for API responses only?