Implement Phase 3 of v1.3.0 tags feature per microformats-tags-design.md:
Routes (starpunk/routes/public.py):
- Add /tag/<tag> archive route with normalization and 404 handling
- Pre-load tags in index route for all notes
- Pre-load tags in note route for individual notes
Admin (starpunk/routes/admin.py):
- Parse comma-separated tag input in create route
- Parse tag input in update route
- Pre-load tags when displaying edit form
- Empty tag field removes all tags
Templates:
- Add tag input field to templates/admin/edit.html
- Add tag input field to templates/admin/new.html
- Use Jinja2 map filter to display existing tags
Implementation details:
- Tag URL parameter normalized to lowercase before lookup
- Tags pre-loaded using object.__setattr__ pattern (like media)
- parse_tag_input() handles trim, dedupe, normalization
- All existing tests pass (micropub categories, admin routes)
Per architect design:
- No pagination on tag archives (acceptable for v1.3.0)
- No autocomplete in admin (out of scope)
- Follows existing media loading patterns
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Implements tag/category system backend following microformats2 p-category specification.
Database changes:
- Migration 008: Add tags and note_tags tables
- Normalized tag storage (case-insensitive lookup, display name preserved)
- Indexes for performance
New module:
- starpunk/tags.py: Tag management functions
- normalize_tag: Normalize tag strings
- get_or_create_tag: Get or create tag records
- add_tags_to_note: Associate tags with notes (replaces existing)
- get_note_tags: Retrieve note tags (alphabetically ordered)
- get_tag_by_name: Lookup tag by normalized name
- get_notes_by_tag: Get all notes with specific tag
- parse_tag_input: Parse comma-separated tag input
Model updates:
- Note.tags property (lazy-loaded, prefer pre-loading in routes)
- Note.to_dict() add include_tags parameter
CRUD updates:
- create_note() accepts tags parameter
- update_note() accepts tags parameter (None = no change, [] = remove all)
Micropub integration:
- Pass tags to create_note() (tags already extracted by extract_tags())
- Return tags in q=source response
Per design doc: docs/design/v1.3.0/microformats-tags-design.md
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>