+
+}
+```
+
+### Step 4: Helper Function for User Context
+
+**Add this utility function to main.go to reduce boilerplate:**
+
+```go
+// Helper to get user from JWT context
+func getUserFromContext(c echo.Context) templates.User {
+ return templates.User{
+ ID: c.Get("user_id").(string),
+ Email: c.Get("user_email").(string),
+ Username: c.Get("user_username").(string),
+ Role: c.Get("user_role").(string),
+ }
+}
+```
+
+**Now all HTML routes can use this:**
+```go
+protected.GET("/collections", func(c echo.Context) error {
+ user := getUserFromContext(c) // Reusable helper
+ // ... rest of the code
+})
+```
+
+## Route Structure Summary
+
+### Before (Current)
+
+```
+/api/collections → JSON (API)
+/api/collections/:id → JSON (API)
+/dashboard → HTML (no SSR, fetches data)
+/devices → HTML (no SSR, fetches data)
+```
+
+### After (Hybrid)
+
+```
+/api/collections → JSON (API) - UNCHANGED
+/api/collections/:id → JSON (API) - UNCHANGED
+/collections → HTML (SSR) - NEW
+/collections/:id → HTML (SSR) - NEW
+/dashboard → HTML (no SSR) - UNCHANGED
+/devices → HTML (no SSR) - UNCHANGED
+```
+
+## Migration Checklist
+
+For each new page you want to convert to SSR:
+
+- [ ] **Add data-fetching helper** to handler (if doesn't exist)
+- [ ] **Create new HTML route** in `cmd/server/main.go`
+- [ ] **Update template signature** to accept data
+- [ ] **Remove initial fetch()** from template JavaScript
+- [ ] **Keep fetch() for CRUD operations** (create, update, delete)
+- [ ] **Test API routes still work** with curl/Postman
+- [ ] **Test HTML routes** in browser
+
+## Benefits
+
+### Performance
+
+**Before (API-only):**
+1. Browser requests page
+2. Server renders empty HTML (~10ms)
+3. Browser receives HTML
+4. JavaScript fetches data (~50ms)
+5. Server queries database (~20ms)
+6. Server returns JSON (~5ms)
+7. Browser renders data (~10ms)
+**Total: ~95ms visible to user**
+
+**After (SSR):**
+1. Browser requests page
+2. Server queries database (~20ms)
+3. Server renders HTML with data (~10ms)
+4. Browser receives complete HTML
+5. Browser paints page (~10ms)
+**Total: ~40ms visible to user**
+
+**58% faster initial load!**
+
+### Architectural Benefits
+
+1. **Zero logic duplication:** Service layer is single source of truth
+2. **API remains intact:** No breaking changes for mobile/external consumers
+3. **SEO friendly:** Search engines see complete HTML
+4. **Progressive enhancement:** Works without JavaScript
+5. **Easier testing:** Can test API routes independently
+
+## Example: Complete Implementation
+
+### File: internal/handlers/collections.go
+
+**Add these helper methods:**
+
+```go
+// GetCollectionsData returns raw collection data (for both API and HTML)
+func (h *CollectionHandler) GetCollectionsData(c echo.Context) ([]database.GetCollectionsRow, error) {
+ user := c.Get("user").(database.Users)
+ userUUID := uuid.UUID(user.ID.Bytes)
+
+ return h.collectionService.GetUserCollections(
+ c.Request().Context(),
+ userUUID,
+ )
+}
+
+// GetCollectionData returns single collection (for both API and HTML)
+func (h *CollectionHandler) GetCollectionData(c echo.Context, collectionID uuid.UUID) (database.Collections, error) {
+ return h.collectionService.GetCollection(c.Request().Context(), collectionID)
+}
+
+// GetCollectionBooksData returns books in collection (for both API and HTML)
+func (h *CollectionHandler) GetCollectionBooksData(c echo.Context, collectionID uuid.UUID) ([]database.GetCollectionBooksRow, error) {
+ return h.collectionService.GetCollectionBooks(c.Request().Context(), collectionID)
+}
+```
+
+**Update existing API handlers to use helpers:**
+
+```go
+// Before
+func (h *CollectionHandler) GetCollections(c echo.Context) error {
+ user := c.Get("user").(database.Users)
+ userUUID := uuid.UUID(user.ID.Bytes)
+ collections, err := h.collectionService.GetUserCollections(...)
+ // ...
+}
+
+// After
+func (h *CollectionHandler) GetCollections(c echo.Context) error {
+ collections, err := h.GetCollectionsData(c)
+ // Format and return JSON
+ // ...
+}
+```
+
+### File: cmd/server/main.go
+
+**Add after line 258 (after collections API routes):**
+
+```go
+// ===== HTML ROUTES WITH SSR =====
+// These provide fast initial page loads while keeping /api/* routes intact
+
+// Collections list page
+protected.GET("/collections", func(c echo.Context) error {
+ user := getUserFromContext(c)
+
+ collections, err := collectionHandler.GetCollectionsData(c)
+ if err != nil {
+ return c.HTML(500, "Error loading collections")
+ }
+
+ type ColData struct {
+ ID string
+ Name string
+ Description string
+ Color string
+ Icon string
+ }
+
+ data := make([]ColData, len(collections))
+ for i, col := range collections {
+ data[i] = ColData{
+ ID: uuid.UUID(col.ID.Bytes).String(),
+ Name: col.Name,
+ Description: textToString(col.Description),
+ Color: textToString(col.Color),
+ Icon: textToString(col.Icon),
+ }
+ }
+
+ var buf bytes.Buffer
+ templates.Collections(user, data).Render(c.Request().Context(), &buf)
+ return c.HTML(200, buf.String())
+})
+
+// Collection detail page
+protected.GET("/collections/:id", func(c echo.Context) error {
+ user := getUserFromContext(c)
+ collectionID, _ := uuid.Parse(c.Param("id"))
+
+ collection, err := collectionHandler.GetCollectionData(c, collectionID)
+ if err != nil {
+ return c.HTML(404, "Collection not found")
+ }
+
+ books, err := collectionHandler.GetCollectionBooksData(c, collectionID)
+ if err != nil {
+ return c.HTML(500, "Error loading books")
+ }
+
+ var buf bytes.Buffer
+ templates.CollectionDetail(user, collection, books).Render(c.Request().Context(), &buf)
+ return c.HTML(200, buf.String())
+})
+```
+
+### File: templates/collections.templ
+
+**Update template signature:**
+
+```templ
+// Before
+templ Collections(user User) {
+
+
+}
+
+// After
+templ Collections(user User, collections []Collection) {
+
+ for col := range collections {
+
+
+
+
{ col.Name }
+
{ col.Description }
+
+
+
+
+
+
+
+
+ }
+
+
+
+}
+```
+
+## Testing Checklist
+
+### API Routes (Must Remain Unchanged)
+
+```bash
+# Test collections API
+curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8765/api/collections
+# Should return JSON as before
+
+curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8765/api/collections/ID
+# Should return JSON as before
+```
+
+### HTML Routes (New)
+
+```bash
+# Test HTML route in browser
+open http://localhost:8765/collections
+# Should show complete page instantly with data
+```
+
+### Verify No Breaking Changes
+
+1. **Mobile apps still work:** Test with existing mobile app or API client
+2. **External consumers still work:** Test with Postman/curl
+3. **Database queries unchanged:** Monitor query logs
+4. **Business logic unchanged:** Test collection creation, updates, deletion
+
+## Common Pitfalls
+
+### ❌ Don't: Modify Existing API Routes
+
+```go
+// WRONG: Don't change existing API handler
+func (h *CollectionHandler) GetCollections(c echo.Context) error {
+ // Don't add HTML rendering here!
+ if wantsHTML(c) {
+ return renderHTML(...)
+ }
+ return c.JSON(...)
+}
+```
+
+**Why:** Breaks API consumers, adds complexity to API routes.
+
+### ✅ Do: Create Separate HTML Routes
+
+```go
+// RIGHT: Keep API handler simple, add separate HTML route
+func (h *CollectionHandler) GetCollections(c echo.Context) error {
+ data, _ := h.GetCollectionsData(c)
+ return c.JSON(200, data) // JSON only
+}
+
+// Separate HTML route in main.go
+protected.GET("/collections", func(c echo.Context) error {
+ data, _ := collectionHandler.GetCollectionsData(c)
+ return render(c, templates.Collections(user, data))
+})
+```
+
+**Why:** Clean separation, API stays simple, HTML routes are independent.
+
+### ❌ Don't: Duplicate Business Logic
+
+```go
+// WRONG: Duplicating queries
+func (h *CollectionHandler) GetCollections(c echo.Context) error {
+ collections, _ := h.db.GetCollections(...) // Query here
+ return c.JSON(200, collections)
+}
+
+protected.GET("/collections", func(c echo.Context) error {
+ collections, _ := h.db.GetCollections(...) // Same query again!
+ return render(c, templates.Collections(user, collections))
+})
+```
+
+**Why:** Duplication, harder to maintain, bugs appear twice.
+
+### ✅ Do: Share Service Layer
+
+```go
+// RIGHT: Both use same service method
+func (h *CollectionHandler) GetCollectionsData(c echo.Context) {
+ return h.collectionService.GetUserCollections(...) // Single source
+}
+
+func (h *CollectionHandler) GetCollections(c echo.Context) error {
+ data, _ := h.GetCollectionsData(c)
+ return c.JSON(200, data)
+}
+
+protected.GET("/collections", func(c echo.Context) error {
+ data, _ := collectionHandler.GetCollectionsData(c)
+ return render(c, templates.Collections(user, data))
+})
+```
+
+**Why:** Single source of truth, DRY principle, easier maintenance.
+
+## Rollback Plan
+
+If anything goes wrong, rollback is trivial:
+
+1. **Remove new HTML routes:** Delete the `protected.GET("/collections", ...)` blocks from main.go
+2. **Revert template changes:** Change `templ Collections(user, collections)` back to `templ Collections(user)`
+3. **API routes untouched:** No changes made to `/api/*` routes
+
+**Zero risk to existing functionality.**
+
+## Next Steps
+
+This document provides the architectural foundation. Before implementing Phase 9 (Collections UI), the codebase should be migrated to this hybrid pattern for consistency:
+
+1. Migrate existing pages (`/dashboard`, `/devices`) to use SSR
+2. Add helper methods to all handlers
+3. Update all templates to accept data
+4. Test thoroughly
+
+Once the pattern is established, Phase 9 implementation will be straightforward and follow the same conventions.
+
+---
+
+**Document Version:** 1.0
+**Last Updated:** 2025-01-31
+**Status:** Ready for Implementation
diff --git a/PROJECT_GUIDELINES.md b/PROJECT_GUIDELINES.md
new file mode 100644
index 0000000..52dfeb9
--- /dev/null
+++ b/PROJECT_GUIDELINES.md
@@ -0,0 +1,176 @@
+# Combined Project Guidelines for Bookmann
+
+## 🚨 CRITICAL PROHIBITIONS (Never violate these)
+
+### Backend & Database
+- ❌ **NEVER modify backend code when working on frontend-only tasks**
+- ❌ **NEVER modify database schema** unless explicitly instructed for full-stack changes
+- ❌ **NEVER use Docker** - use Podman only
+- ❌ **NEVER build server binaries locally** - all builds through Dockerfile/docker-compose
+- ❌ **NEVER create new migration files** - merge changes into current one until release
+- ❌ **NEVER use `git checkout` on schema files** without checking what will be lost
+- ❌ **NEVER break existing functionality** unless explicitly instructed
+
+### Frontend & Styling
+- ❌ **NEVER modify backend/API for frontend features without user confirmation**
+- ❌ **NEVER use custom CSS** - TailwindCSS classes only
+- ❌ **NEVER use JavaScript** - convert all to TypeScript
+- ❌ **NEVER use object-oriented programming** patterns - use functional/other paradigms
+- ❌ **NEVER add new Dockerfiles without user confirmation**
+
+### General
+- ❌ **NEVER skip pre-commit hooks** unless explicitly requested
+- ❌ **NEVER force push to main/master** branches
+- ❌ **NEVER commit files with secrets** (.env, credentials.json, etc.)
+- ❌ **NEVER make assumptions** - ask clarifying questions when uncertain
+
+---
+
+## 🎯 CONTEXT-SPECIFIC RULES
+
+### When Working on Frontend-Only Tasks
+- **DO NOT touch backend code** - handlers, services, database layer
+- **DO NOT modify API routes** - use existing endpoints only
+- **DO NOT change database schema** - work with existing structure
+- **If backend change seems necessary**:
+ 1. Identify the required change
+ 2. Explain why you need it
+ 3. Provide impact analysis
+ 4. **ASK FOR USER CONFIRMATION before proceeding**
+
+### When Working on Full-Stack Tasks
+- Backend changes are allowed when explicitly part of the task
+- Still follow all database protocols (atomic changes, validation, etc.)
+- Still use Podman for all builds
+- Still include Bruno tests for API changes
+
+---
+
+## ✅ MANDATORY REQUIREMENTS
+
+### Database Operations (Full-Stack Tasks Only)
+- ✅ Follow **pgx v5 standards** for all database operations
+- ✅ Treat schema changes as **ATOMIC** - complete success or complete rejection
+- ✅ When schema changes occur: delete database and rebuild with clean Podman cache
+- ✅ Use **pre-change checklist**: read schema → identify columns → plan changes → verify → read back
+- ✅ **Post-change validation**: ensure schema.sql, models.go, and queries.sql are in sync
+
+### Build & Deployment
+- ✅ Use **Podman** exclusively (not Docker)
+- ✅ All builds through existing **Dockerfile** and **docker-compose.yml**
+- ✅ Stop building server binaries - everything goes through containers
+
+### API Changes (Full-Stack Tasks Only)
+- ✅ Include **Bruno v3.0 .bru requests** with all API documentation
+- ✅ Tests must be **comprehensive and cover three contexts**: no user, user, and admin
+- ✅ Maintain backward compatibility for mobile apps and external consumers
+
+### Frontend & Styling
+- ✅ Always use **TailwindCSS classes** for all styling
+- ✅ Convert all JavaScript to **TypeScript**
+- ✅ Avoid OOP patterns - prefer functional/other paradigms
+
+### Code Organization
+- ✅ Minimize project structure changes
+- ✅ Place new files in **contextually appropriate directories**
+- ✅ Follow **KISS**, **DRY**, and **YAGNI** principles
+- ✅ Use **multiple, logical git commits** with clear messages
+
+### Configuration & Environment
+- ✅ If **.env is missing**, auto-generate secure values
+- ✅ Never commit secrets to repository
+
+### Documentation
+- ✅ Update **README.md** when users/admins need to be informed
+- ✅ Document API changes with Bruno collections
+
+### Process & Continuity
+- ✅ If mid-task and receive "no response", **continue the task**
+- ✅ Verify no regressions before modifying/removing code
+
+---
+
+## 🔧 TECHNICAL STANDARDS
+
+### Backend Stack
+- **Language**: Go 1.25+
+- **Database**: PostgreSQL 15+ with **pgx v5 driver** only
+- **Authentication**: JWT tokens with bcrypt password hashing
+- **Architecture**: Service layer pattern (handlers → services → database)
+
+### Frontend Stack
+- **Styling**: TailwindCSS (no custom CSS)
+- **Language**: TypeScript (no JavaScript)
+- **Templates**: HTMX with server-side rendering
+- **Patterns**: Functional/other (no OOP)
+
+### Containerization
+- **Runtime**: Podman (not Docker)
+- **Build**: Existing Dockerfile and docker-compose.yml only
+- **No local builds** allowed
+
+---
+
+## 📋 WORKFLOW CHECKLISTS
+
+### Before Making Frontend-Only Changes
+- [ ] Identify if backend modification could make implementation simpler
+- [ ] Plan to use existing API endpoints only
+- [ ] If backend change seems necessary, prepare confirmation request:
+ - Required change description
+ - Why it would help
+ - Impact analysis
+ - Alternative approaches considered
+- [ ] Plan git commit structure (multiple logical commits)
+- [ ] Identify if README.md needs updates
+
+### Before Making Full-Stack Changes
+- [ ] Read current schema completely (if database changes)
+- [ ] Identify all columns that must be preserved
+- [ ] Plan exact changes needed
+- [ ] Verify Podman will be used for builds
+- [ ] Plan git commit structure (multiple logical commits)
+
+### During Schema Changes (Full-Stack Only)
+- [ ] Read current schema completely
+- [ ] Identify all columns that must be preserved
+- [ ] Plan exact changes needed
+- [ ] Set up verification step
+- [ ] Make intended changes
+- [ ] Immediately verify by reading back modified sections
+- [ ] Confirm ALL expected columns are present
+- [ ] Verify schema.sql, models.go, and queries.sql are in sync
+
+### After API Changes
+- [ ] Create/update Bruno v3.0 .bru requests
+- [ ] Test with no user context
+- [ ] Test with regular user context
+- [ ] Test with admin context
+- [ ] Verify backward compatibility
+
+### Before Committing
+- [ ] Run tests: `go test ./... -v`
+- [ ] Run lint/typecheck if available
+- [ ] Ensure no secrets in changes
+- [ ] Verify logical commit structure
+- [ ] Update README.md if user-facing changes
+
+---
+
+## 🏗 ARCHITECTURAL PATTERNS
+
+### Current: API-Driven Frontend
+```
+Browser → Go template (empty) → JavaScript fetch() → API → Database
+```
+
+### Future Reference: Hybrid SSR (NOT TO IMPLEMENT YET)
+```
+Browser → Go template (with data) → Display instantly
+ ↓
+ JavaScript only for interactivity (CRUD)
+ ↓
+ Shared service layer
+```
+
+Ultimately, whenever you are unsure just ask for confirmation.