Complete implementation of v1.2.0 "IndieWeb Features" release. ## Phase 1: Custom Slugs - Optional custom slug field in note creation form - Auto-sanitization (lowercase, hyphens only) - Uniqueness validation with auto-numbering - Read-only after creation to preserve permalinks - Matches Micropub mp-slug behavior ## Phase 2: Author Discovery + Microformats2 - Automatic h-card discovery from IndieAuth identity URL - 24-hour caching with graceful fallback - Never blocks login (per ADR-061) - Complete h-entry, h-card, h-feed markup - All required Microformats2 properties - rel-me links for identity verification - Passes IndieWeb validation ## Phase 3: Media Upload - Upload up to 4 images per note (JPEG, PNG, GIF, WebP) - Automatic optimization with Pillow - Auto-resize to 2048px - EXIF orientation correction - 95% quality compression - Social media-style layout (media top, text below) - Optional captions for accessibility - Integration with all feed formats (RSS, ATOM, JSON Feed) - Date-organized storage with UUID filenames - Immutable caching (1 year) ## Database Changes - migrations/006_add_author_profile.sql - Author discovery cache - migrations/007_add_media_support.sql - Media storage ## New Modules - starpunk/author_discovery.py - h-card discovery and caching - starpunk/media.py - Image upload, validation, optimization ## Documentation - 4 new ADRs (056, 057, 058, 061) - Complete design specifications - Developer Q&A with 40+ questions answered - 3 implementation reports - 3 architect reviews (all approved) ## Testing - 56 new tests for v1.2.0 features - 842 total tests in suite - All v1.2.0 feature tests passing ## Dependencies - Added: mf2py (Microformats2 parser) - Added: Pillow (image processing) Version: 1.2.0-rc.1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
303 lines
13 KiB
Markdown
303 lines
13 KiB
Markdown
# v1.2.0 Developer Q&A
|
||
|
||
**Date**: 2025-11-28
|
||
**Architect**: StarPunk Architect Subagent
|
||
**Purpose**: Answer critical implementation questions for v1.2.0
|
||
|
||
## Custom Slugs Answers
|
||
|
||
**Q1: Validation pattern conflict - should we apply new lowercase validation to existing slugs?**
|
||
- **Answer:** Validate only new custom slugs, don't migrate existing slugs
|
||
- **Rationale:** Existing slugs work, no need to change them retroactively
|
||
- **Implementation:** In `validate_and_sanitize_custom_slug()`, apply lowercase enforcement only to new/edited slugs
|
||
|
||
**Q2: Form field readonly behavior - how should the slug field behave on edit forms?**
|
||
- **Answer:** Display as readonly input field with current value visible
|
||
- **Rationale:** Users need to see the current slug but understand it cannot be changed
|
||
- **Implementation:** Use `readonly` attribute, not `disabled` (disabled fields don't submit with form)
|
||
|
||
**Q3: Slug uniqueness validation - where should this happen?**
|
||
- **Answer:** Both client-side (for UX) and server-side (for security)
|
||
- **Rationale:** Client-side prevents unnecessary submissions, server-side is authoritative
|
||
- **Implementation:** Database unique constraint + Python validation in `validate_and_sanitize_custom_slug()`
|
||
|
||
## Media Upload Answers
|
||
|
||
**Q4: Media upload flow - how should upload and note association work?**
|
||
- **Answer:** Upload during note creation, associate via note_id after creation
|
||
- **Rationale:** Simpler than pre-upload with temporary IDs
|
||
- **Implementation:** Upload files in `create_note_submit()` after note is created, store associations in media table
|
||
|
||
**Q5: Storage directory structure - exact path format?**
|
||
- **Answer:** `data/media/YYYY/MM/filename-uuid.ext`
|
||
- **Rationale:** Date organization helps with backups and management
|
||
- **Implementation:** Use `os.makedirs(path, exist_ok=True)` to create directories as needed
|
||
|
||
**Q6: File naming convention - how to ensure uniqueness?**
|
||
- **Answer:** `{original_name_slug}-{uuid4()[:8]}.{extension}`
|
||
- **Rationale:** Preserves original name for SEO while ensuring uniqueness
|
||
- **Implementation:** Slugify original filename, append 8-char UUID, preserve extension
|
||
|
||
**Q7: MIME type validation - which types exactly?**
|
||
- **Answer:** Allow: image/jpeg, image/png, image/gif, image/webp. Reject all others
|
||
- **Rationale:** Common web formats only, no SVG (XSS risk)
|
||
- **Implementation:** Use python-magic for reliable MIME detection, not just file extension
|
||
|
||
**Q8: Upload size limits - what's reasonable?**
|
||
- **Answer:** 10MB per file, 40MB total per note (4 files × 10MB)
|
||
- **Rationale:** Sufficient for high-quality images without overwhelming storage
|
||
- **Implementation:** Check in both client-side JavaScript and server-side validation
|
||
|
||
**Q9: Database schema for media table - exact columns?**
|
||
- **Answer:** id, note_id, filename, mime_type, size_bytes, width, height, uploaded_at
|
||
- **Rationale:** Minimal but sufficient metadata for display and management
|
||
- **Implementation:** Use Pillow to extract image dimensions on upload
|
||
|
||
**Q10: Orphaned file cleanup - how to handle?**
|
||
- **Answer:** Keep orphaned files, add admin cleanup tool in future version
|
||
- **Rationale:** Data preservation is priority, cleanup can be manual for v1.2.0
|
||
- **Implementation:** Log orphaned files but don't auto-delete
|
||
|
||
**Q11: Upload progress indication - required for v1.2.0?**
|
||
- **Answer:** No, simple form submission is sufficient for v1.2.0
|
||
- **Rationale:** Keep it simple, can enhance in future version
|
||
- **Implementation:** Standard HTML form with enctype="multipart/form-data"
|
||
|
||
**Q12: Image display order - how to maintain?**
|
||
- **Answer:** Use upload sequence, store display_order in media table
|
||
- **Rationale:** Predictable and simple
|
||
- **Implementation:** Auto-increment display_order starting at 0
|
||
|
||
**Q13: Thumbnail generation - needed for v1.2.0?**
|
||
- **Answer:** No, use CSS for responsive sizing
|
||
- **Rationale:** Simplicity over optimization for v1
|
||
- **Implementation:** Use `max-width: 100%` and lazy loading
|
||
|
||
**Q14: Edit form media handling - can users remove media?**
|
||
- **Answer:** Yes, checkbox to mark for deletion
|
||
- **Rationale:** Essential editing capability
|
||
- **Implementation:** "Remove" checkboxes next to each image in edit form
|
||
|
||
**Q15: Media URL structure - exact format?**
|
||
- **Answer:** `/media/YYYY/MM/filename.ext` (matches storage path)
|
||
- **Rationale:** Clean URLs, date organization visible
|
||
- **Implementation:** Route in `starpunk/routes/public.py` using send_from_directory
|
||
|
||
## Author Discovery Answers
|
||
|
||
**Q16: Discovery failure handling - what if profile URL is unreachable?**
|
||
- **Answer:** Use defaults: name from IndieAuth me URL domain, no photo
|
||
- **Rationale:** Always provide something, never break
|
||
- **Implementation:** Try discovery, catch all exceptions, use defaults
|
||
|
||
**Q17: h-card parsing library - which one?**
|
||
- **Answer:** Use mf2py (already in requirements for Micropub)
|
||
- **Rationale:** Already a dependency, well-maintained
|
||
- **Implementation:** `import mf2py; result = mf2py.parse(url=profile_url)`
|
||
|
||
**Q18: Multiple h-cards on profile - which to use?**
|
||
- **Answer:** First h-card with url property matching the profile URL
|
||
- **Rationale:** Most specific match per IndieWeb convention
|
||
- **Implementation:** Loop through h-cards, check url property
|
||
|
||
**Q19: Discovery caching duration - how long?**
|
||
- **Answer:** 24 hours, with manual refresh button in admin
|
||
- **Rationale:** Balance between freshness and performance
|
||
- **Implementation:** Store discovered_at timestamp, check age
|
||
|
||
**Q20: Profile update mechanism - when to refresh?**
|
||
- **Answer:** On login + manual refresh button + 24hr expiry
|
||
- **Rationale:** Login is natural refresh point
|
||
- **Implementation:** Call discovery in auth callback
|
||
|
||
**Q21: Missing properties handling - what if no name/photo?**
|
||
- **Answer:** name = domain from URL, photo = None (no image)
|
||
- **Rationale:** Graceful degradation
|
||
- **Implementation:** Use get() with defaults on parsed properties
|
||
|
||
**Q22: Database schema for author_profile - exact columns?**
|
||
- **Answer:** me_url (PK), name, photo, url, discovered_at, raw_data (JSON)
|
||
- **Rationale:** Cache parsed data + raw for debugging
|
||
- **Implementation:** Single row table, upsert on discovery
|
||
|
||
## Microformats2 Answers
|
||
|
||
**Q23: h-card placement - where exactly in templates?**
|
||
- **Answer:** Only within h-entry author property (p-author h-card)
|
||
- **Rationale:** Correct semantic placement per spec
|
||
- **Implementation:** In note partial template, not standalone
|
||
|
||
**Q24: h-feed container - which pages need it?**
|
||
- **Answer:** Homepage (/) and any paginated list pages
|
||
- **Rationale:** Feed pages only, not single note pages
|
||
- **Implementation:** Wrap note list in div.h-feed with h1.p-name
|
||
|
||
**Q25: Optional properties - which to include?**
|
||
- **Answer:** Only what we have: author, name, url, published, content
|
||
- **Rationale:** Don't add empty properties
|
||
- **Implementation:** Use conditional template blocks
|
||
|
||
**Q26: Micropub compatibility - any changes needed?**
|
||
- **Answer:** No, Micropub already handles microformats correctly
|
||
- **Rationale:** Micropub creates data, templates display it
|
||
- **Implementation:** Ensure templates match Micropub's data model
|
||
|
||
## Feed Integration Answers
|
||
|
||
**Q27: RSS/Atom changes for media - how to include images?**
|
||
- **Answer:** Add as enclosures (RSS) and link rel="enclosure" (Atom)
|
||
- **Rationale:** Standard podcast/media pattern
|
||
- **Implementation:** Loop through note.media, add enclosure elements
|
||
|
||
**Q28: JSON Feed media handling - which property?**
|
||
- **Answer:** Use "attachments" array per JSON Feed 1.1 spec
|
||
- **Rationale:** Designed for exactly this use case
|
||
- **Implementation:** Create attachment objects with url, mime_type
|
||
|
||
**Q29: Feed caching - any changes needed?**
|
||
- **Answer:** No, existing cache logic is sufficient
|
||
- **Rationale:** Media URLs are stable once uploaded
|
||
- **Implementation:** No changes required
|
||
|
||
**Q30: Author in feeds - use discovered data?**
|
||
- **Answer:** Yes, use discovered name and photo in feed metadata
|
||
- **Rationale:** Consistency across all outputs
|
||
- **Implementation:** Pass author_profile to feed templates
|
||
|
||
## Database Migration Answers
|
||
|
||
**Q31: Migration naming convention - what number?**
|
||
- **Answer:** Use next sequential: 005_add_media_support.sql
|
||
- **Rationale:** Continue existing pattern
|
||
- **Implementation:** Check latest migration, increment
|
||
|
||
**Q32: Migration rollback - needed?**
|
||
- **Answer:** No, forward-only migrations per project convention
|
||
- **Rationale:** Simplicity, follows existing pattern
|
||
- **Implementation:** CREATE IF NOT EXISTS, never DROP
|
||
|
||
**Q33: Migration testing - how to verify?**
|
||
- **Answer:** Test on copy of production database
|
||
- **Rationale:** Real-world data is best test
|
||
- **Implementation:** Copy data/starpunk.db, run migration, verify
|
||
|
||
## Testing Strategy Answers
|
||
|
||
**Q34: Test data for media - what to use?**
|
||
- **Answer:** Generate 1x1 pixel PNG in tests, don't use real files
|
||
- **Rationale:** Minimal, fast, no binary files in repo
|
||
- **Implementation:** Use Pillow to generate test images in memory
|
||
|
||
**Q35: Author discovery mocking - how to test?**
|
||
- **Answer:** Mock HTTP responses with test h-card HTML
|
||
- **Rationale:** Deterministic, no external dependencies
|
||
- **Implementation:** Use responses library or unittest.mock
|
||
|
||
**Q36: Integration test priority - which are critical?**
|
||
- **Answer:** Upload → Display → Edit → Delete flow
|
||
- **Rationale:** Core user journey must work
|
||
- **Implementation:** Single test that exercises full lifecycle
|
||
|
||
## Error Handling Answers
|
||
|
||
**Q37: Upload failure recovery - how to handle?**
|
||
- **Answer:** Show error, preserve form data, allow retry
|
||
- **Rationale:** Don't lose user's work
|
||
- **Implementation:** Flash error, return to form with content preserved
|
||
|
||
**Q38: Discovery network timeout - how long to wait?**
|
||
- **Answer:** 5 second timeout for profile fetch
|
||
- **Rationale:** Balance between patience and responsiveness
|
||
- **Implementation:** Use requests timeout parameter
|
||
|
||
## Deployment Answers
|
||
|
||
**Q39: Media directory permissions - what's needed?**
|
||
- **Answer:** data/media/ needs write permission for app user
|
||
- **Rationale:** Same as existing data/ directory
|
||
- **Implementation:** Document in deployment guide, create in setup
|
||
|
||
**Q40: Upgrade path from v1.1.2 - any special steps?**
|
||
- **Answer:** Run migration, create media directory, restart app
|
||
- **Rationale:** Minimal disruption
|
||
- **Implementation:** Add to CHANGELOG upgrade notes
|
||
|
||
**Q41: Configuration changes - any new env vars?**
|
||
- **Answer:** No, all settings have sensible defaults
|
||
- **Rationale:** Maintain zero-config philosophy
|
||
- **Implementation:** Hardcode limits in code with constants
|
||
|
||
## Critical Path Decisions Summary
|
||
|
||
These are the key decisions to unblock implementation:
|
||
|
||
1. **Media upload flow**: Upload after note creation, associate via note_id
|
||
2. **Author discovery**: Use mf2py, cache for 24hrs, graceful fallbacks
|
||
3. **h-card parsing**: First h-card with matching URL property
|
||
4. **h-card placement**: Only within h-entry as p-author
|
||
5. **Migration strategy**: Sequential numbering (005), forward-only
|
||
|
||
## Implementation Order
|
||
|
||
Based on dependencies and complexity:
|
||
|
||
### Phase 1: Custom Slugs (2 hours)
|
||
- Simplest feature
|
||
- No database changes
|
||
- Template and validation only
|
||
|
||
### Phase 2: Author Discovery (4 hours)
|
||
- Build discovery module
|
||
- Add author_profile table
|
||
- Integrate with auth flow
|
||
- Update templates
|
||
|
||
### Phase 3: Media Upload (6 hours)
|
||
- Most complex feature
|
||
- Media table and migration
|
||
- Upload handling
|
||
- Template updates
|
||
- Storage management
|
||
|
||
## File Structure
|
||
|
||
Key files to create/modify:
|
||
|
||
### New Files
|
||
- `starpunk/discovery.py` - Author discovery module
|
||
- `starpunk/media.py` - Media handling module
|
||
- `migrations/005_add_media_support.sql` - Database changes
|
||
- `static/js/media-upload.js` - Optional enhancement
|
||
|
||
### Modified Files
|
||
- `templates/admin/new.html` - Add slug and media fields
|
||
- `templates/admin/edit.html` - Add slug (readonly) and media
|
||
- `templates/partials/note.html` - Add microformats markup
|
||
- `templates/public/index.html` - Add h-feed container
|
||
- `starpunk/routes/admin.py` - Handle slugs and uploads
|
||
- `starpunk/routes/auth.py` - Trigger discovery on login
|
||
- `starpunk/models/note.py` - Add media relationship
|
||
|
||
## Success Metrics
|
||
|
||
Implementation is complete when:
|
||
|
||
1. ✅ Custom slug can be specified on creation
|
||
2. ✅ Images can be uploaded and displayed
|
||
3. ✅ Author info is discovered from IndieAuth profile
|
||
4. ✅ IndieWebify.me validates h-feed and h-entry
|
||
5. ✅ All tests pass
|
||
6. ✅ No regressions in existing functionality
|
||
7. ✅ Media files are tracked in database
|
||
8. ✅ Errors are handled gracefully
|
||
|
||
## Final Notes
|
||
|
||
- Keep it simple - this is v1.2.0, not v2.0.0
|
||
- Data preservation over premature optimization
|
||
- When uncertain, choose the more explicit option
|
||
- Document any deviations from this guidance
|
||
|
||
---
|
||
|
||
This Q&A document serves as the authoritative implementation guide for v1.2.0. Any questions not covered here should follow the principle of maximum simplicity. |