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>
6.9 KiB
v1.2.0 Phase 1: Custom Slugs - Implementation Report
Date: 2025-11-28 Developer: StarPunk Fullstack Developer Subagent Phase: v1.2.0 Phase 1 of 3 Status: Complete
Summary
Implemented custom slug input field in the web UI note creation form, allowing users to specify custom slugs when creating notes. This brings the web UI to feature parity with the Micropub API's mp-slug property.
Implementation Overview
What Was Implemented
-
Custom Slug Input Field (templates/admin/new.html)
- Added optional text input field for custom slugs
- HTML5 pattern validation for client-side guidance
- Helpful placeholder and helper text
- Positioned between content field and publish checkbox
-
Read-Only Slug Display (templates/admin/edit.html)
- Shows current slug as disabled input field
- Includes explanation that slugs cannot be changed
- Preserves permalink integrity
-
Route Handler Updates (starpunk/routes/admin.py)
- Updated
create_note_submit()to acceptcustom_slugform parameter - Passes custom slug to
create_note()function - Uses existing slug validation from
slug_utils.py
- Updated
-
Comprehensive Test Suite (tests/test_custom_slugs.py)
- 30 tests covering all aspects of custom slug functionality
- Tests validation, sanitization, uniqueness, web UI, and edge cases
- Verifies consistency with Micropub behavior
Technical Details
Backend Integration
The implementation leverages existing infrastructure:
- Slug validation: Uses
slug_utils.validate_and_sanitize_custom_slug() - Slug sanitization: Auto-converts to lowercase, removes invalid characters
- Uniqueness checking: Handled by existing
make_slug_unique_with_suffix() - Error handling: Graceful fallbacks for reserved slugs, hierarchical paths, emoji
Frontend Behavior
New Note Form:
<input type="text"
id="custom_slug"
name="custom_slug"
pattern="[a-z0-9-]+"
placeholder="leave-blank-for-auto-generation">
Edit Note Form:
<input type="text"
id="slug"
value="{{ note.slug }}"
readonly
disabled>
Validation Rules
Per slug_utils.py:
- Lowercase letters only
- Numbers allowed
- Hyphens allowed (not consecutive, not leading/trailing)
- Max length: 200 characters
- Reserved slugs: api, admin, auth, feed, static, etc.
Error Handling
- Hierarchical paths (e.g., "path/to/note"): Rejected with error message
- Reserved slugs: Auto-suffixed (e.g., "api" becomes "api-note")
- Invalid characters: Sanitized to valid format
- Duplicates: Auto-suffixed with sequential number (e.g., "slug-2")
- Unicode/emoji: Falls back to timestamp-based slug
Test Results
All 30 tests passing:
tests/test_custom_slugs.py::TestCustomSlugValidation (15 tests)
tests/test_custom_slugs.py::TestCustomSlugWebUI (9 tests)
tests/test_custom_slugs.py::TestCustomSlugMatchesMicropub (2 tests)
tests/test_custom_slugs.py::TestCustomSlugEdgeCases (4 tests)
Test Coverage
Validation Tests:
- Lowercase conversion
- Invalid character sanitization
- Consecutive hyphen removal
- Leading/trailing hyphen trimming
- Unicode normalization
- Reserved slug detection
- Hierarchical path rejection
Web UI Tests:
- Custom slug creation
- Auto-generation fallback
- Uppercase conversion
- Invalid character handling
- Duplicate slug handling
- Reserved slug handling
- Hierarchical path error
- Read-only display in edit form
- Field presence in new form
Micropub Consistency Tests:
- Same validation rules
- Same sanitization behavior
Edge Case Tests:
- Empty slug
- Whitespace-only slug
- Emoji slug (timestamp fallback)
- Unicode slug normalization
Files Modified
Modified Files
templates/admin/new.html- Added custom slug input fieldtemplates/admin/edit.html- Added read-only slug displaystarpunk/routes/admin.py- Updated route handlerCHANGELOG.md- Added entry for v1.2.0 Phase 1
New Files
tests/test_custom_slugs.py- Comprehensive test suite (30 tests)docs/reports/2025-11-28-v1.2.0-phase1-custom-slugs.md- This report
Unchanged Files (Used)
starpunk/notes.py- Already hadcustom_slugparameterstarpunk/slug_utils.py- Already had validation functions
Design Decisions
Why Read-Only in Edit Form?
Per developer Q&A Q2 and Q7:
- Changing slugs breaks permalinks
- Users need to see current slug
- Using
readonly+disabledprevents form submission - Clear explanatory text prevents confusion
Why Same Validation as Micropub?
Per developer Q&A Q39:
- Consistency across all note creation methods
- Users shouldn't get different results from web UI vs API
- Reusing existing validation reduces bugs
Why Auto-Sanitize Instead of Reject?
Per developer Q&A Q3 and slug_utils design:
- Better user experience (helpful vs. frustrating)
- Follows "be liberal in what you accept" principle
- Timestamp fallback ensures notes are never rejected
- Matches Micropub behavior (Q8: never fail requests)
User Experience
Creating a Note with Custom Slug
- User fills in content
- (Optional) User enters custom slug
- System auto-sanitizes slug (lowercase, remove invalid chars)
- System checks uniqueness, adds suffix if needed
- Note created with custom or auto-generated slug
- Success message shows final slug
Creating a Note Without Custom Slug
- User fills in content
- User leaves slug field blank
- System auto-generates slug from first 5 words
- System checks uniqueness, adds suffix if needed
- Note created with auto-generated slug
Editing a Note
- User opens edit form
- Slug shown as disabled field
- User can see but not change slug
- Helper text explains why
Compliance with Requirements
✅ Custom slug field in note creation form
✅ Field is optional (auto-generate if empty)
✅ Field is read-only on edit (prevent permalink breaks)
✅ Validate slug format: ^[a-z0-9-]+$
✅ Auto-sanitize input (convert to lowercase, replace invalid chars)
✅ Check uniqueness before saving
✅ Show helpful error messages
✅ Tests passing
✅ CHANGELOG updated
✅ Implementation report created
Next Steps
This completes Phase 1 of v1.2.0. The remaining phases are:
Phase 2: Author Discovery + Microformats2 (4 hours)
- Implement h-card discovery from IndieAuth profile
- Add author_profile database table
- Update templates with microformats2 markup
- Integrate discovery with auth flow
Phase 3: Media Upload (6 hours)
- Add media upload to note creation form
- Implement media handling and storage
- Add media database table and migration
- Update templates to display media
- Add media management in edit form
Notes
- Implementation took approximately 2 hours as estimated
- No blockers encountered
- All existing tests continue to pass
- No breaking changes to existing functionality
- Ready for architect review
Implementation Status: ✅ Complete Tests Status: ✅ All Passing (30/30) Documentation Status: ✅ Complete