Files
StarPunk/docs/design/v1.0.0/custom-slug-bug-implementation.md
Phil Skentelbery f10d0679da feat(tags): Add database schema and tags module (v1.3.0 Phase 1)
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>
2025-12-10 11:24:23 -07:00

7.8 KiB

Custom Slug Bug Fix - Implementation Report

Date: 2025-11-25 Developer: StarPunk Developer Subagent Branch: bugfix/custom-slug-extraction Status: Complete - Ready for Testing

Executive Summary

Successfully fixed the custom slug extraction bug in the Micropub handler. Custom slugs specified via mp-slug parameter are now correctly extracted and used when creating notes.

Problem Statement

Custom slugs specified via the mp-slug property in Micropub requests were being completely ignored. The system was falling back to auto-generated slugs even when a custom slug was provided by the client (e.g., Quill).

Root Cause: mp-slug was being extracted from normalized properties after it had already been filtered out by normalize_properties() which removes all mp-* parameters.

Implementation Details

Files Modified

  1. starpunk/micropub.py (lines 290-307)

    • Moved mp-slug extraction to BEFORE property normalization
    • Added support for both form-encoded and JSON request formats
    • Added clear comments explaining the timing requirement
  2. tests/test_micropub.py (added lines 191-246)

    • Added test_micropub_create_with_custom_slug_form() - tests form-encoded requests
    • Added test_micropub_create_with_custom_slug_json() - tests JSON requests
    • Both tests verify the custom slug is actually used in the created note

Code Changes

Before (Broken)

# Normalize and extract properties
try:
    properties = normalize_properties(data)  # mp-slug gets filtered here!
    content = extract_content(properties)
    title = extract_title(properties)
    tags = extract_tags(properties)
    published_date = extract_published_date(properties)

    # Extract custom slug if provided (Micropub extension)
    custom_slug = None
    if 'mp-slug' in properties:  # BUG: mp-slug not in properties!
        slug_values = properties.get('mp-slug', [])
        if slug_values and len(slug_values) > 0:
            custom_slug = slug_values[0]

After (Fixed)

# Extract mp-slug BEFORE normalizing properties (it's not a property!)
# mp-slug is a Micropub server extension parameter that gets filtered during normalization
custom_slug = None
if isinstance(data, dict) and 'mp-slug' in data:
    # Handle both form-encoded (list) and JSON (could be string or list)
    slug_value = data.get('mp-slug')
    if isinstance(slug_value, list) and slug_value:
        custom_slug = slug_value[0]
    elif isinstance(slug_value, str):
        custom_slug = slug_value

# Normalize and extract properties
try:
    properties = normalize_properties(data)
    content = extract_content(properties)
    title = extract_title(properties)
    tags = extract_tags(properties)
    published_date = extract_published_date(properties)

Why This Fix Works

  1. Extracts before filtering: Gets mp-slug from raw request data before normalize_properties() filters it out
  2. Handles both formats:
    • Form-encoded: mp-slug is a list ["slug-value"]
    • JSON: mp-slug can be string "slug-value" or list ["slug-value"]
  3. Preserves existing flow: The custom_slug variable was already being passed to create_note() correctly
  4. Architecturally correct: Treats mp-slug as a server parameter (not a property), which aligns with Micropub spec

Test Results

Micropub Test Suite

All 13 Micropub tests passed:

tests/test_micropub.py::test_micropub_no_token PASSED
tests/test_micropub.py::test_micropub_invalid_token PASSED
tests/test_micropub.py::test_micropub_insufficient_scope PASSED
tests/test_micropub.py::test_micropub_create_note_form PASSED
tests/test_micropub.py::test_micropub_create_note_json PASSED
tests/test_micropub.py::test_micropub_create_with_name PASSED
tests/test_micropub.py::test_micropub_create_with_categories PASSED
tests/test_micropub.py::test_micropub_create_with_custom_slug_form PASSED  # NEW
tests/test_micropub.py::test_micropub_create_with_custom_slug_json PASSED  # NEW
tests/test_micropub.py::test_micropub_query_config PASSED
tests/test_micropub.py::test_micropub_query_source PASSED
tests/test_micropub.py::test_micropub_missing_content PASSED
tests/test_micropub.py::test_micropub_unsupported_action PASSED

New Test Coverage

Test 1: Form-encoded with custom slug

  • Request: POST /micropub with content=...&mp-slug=my-custom-slug
  • Verifies: Location header ends with /notes/my-custom-slug
  • Verifies: Note exists in database with correct slug

Test 2: JSON with custom slug

  • Request: POST /micropub with JSON body including "mp-slug": "json-custom-slug"
  • Verifies: Location header ends with /notes/json-custom-slug
  • Verifies: Note exists in database with correct slug

Regression Testing

All existing Micropub tests continue to pass, confirming:

  • Authentication still works correctly
  • Scope checking still works correctly
  • Auto-generated slugs still work when no mp-slug provided
  • Content extraction still works correctly
  • Title and category handling still works correctly

Validation Against Requirements

Per the architect's bug report (docs/reports/custom-slug-bug-diagnosis.md):

  • Extract mp-slug from raw request data
  • Extract BEFORE calling normalize_properties()
  • Handle both form-encoded (list) and JSON (string or list) formats
  • Pass custom_slug to create_note()
  • Add tests for both request formats
  • Ensure existing tests still pass

Architecture Compliance

The fix maintains architectural correctness:

  1. Separation of Concerns: mp-slug is correctly treated as a server extension parameter, not a Micropub property
  2. Existing Validation Pipeline: The slug still goes through all validation in create_note():
    • Reserved slug checking
    • Uniqueness checking with suffix generation if needed
    • Sanitization
  3. No Breaking Changes: All existing functionality preserved
  4. Micropub Spec Compliance: Aligns with how mp-* extensions should be handled

Deployment Notes

What to Test in Production

  1. Create note with custom slug via Quill:

    • Use Quill client to create a note
    • Specify a custom slug in the slug field
    • Verify the created note uses your specified slug
  2. Create note without custom slug:

    • Create a note without specifying a slug
    • Verify auto-generation still works
  3. Reserved slug handling:

    • Try to create a note with slug "api" or "admin"
    • Should be rejected with validation error
  4. Duplicate slug handling:

    • Create a note with slug "test-slug"
    • Try to create another with the same slug
    • Should get "test-slug-xxxx" with random suffix

Known Issues

None. The fix is clean and complete.

Version Impact

This fix will be included in v1.1.0-rc.2 (or next release).

Git Information

Branch: bugfix/custom-slug-extraction Commit: 894e5e3 Commit Message: "fix: Extract mp-slug before property normalization"

Files Changed:

  • starpunk/micropub.py (69 insertions, 8 deletions)
  • tests/test_micropub.py (added 2 comprehensive tests)

Next Steps

  1. Merge bugfix/custom-slug-extraction into main
  2. Deploy to production
  3. Test with Quill client in production environment
  4. Update CHANGELOG.md with fix details
  5. Close any related issue tickets

References

  • Bug Diagnosis: /home/phil/Projects/starpunk/docs/reports/custom-slug-bug-diagnosis.md
  • Micropub Spec: https://www.w3.org/TR/micropub/
  • Related ADR: ADR-029 (Micropub Property Mapping)

Conclusion

The custom slug feature is now fully functional. The bug was a simple timing issue in the extraction logic - trying to get mp-slug after it had been filtered out. The fix is clean, well-tested, and maintains all existing functionality while enabling the custom slug feature as originally designed.

The implementation follows the architect's design exactly and adds comprehensive test coverage for future regression prevention.